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        local_verifier::verify,
14    },
15    agave_feature_set::{FEATURE_NAMES, FeatureSet},
16    bip39::{Language, Mnemonic},
17    clap::{App, AppSettings, Arg, ArgMatches, SubCommand},
18    log::*,
19    solana_account_decoder::{UiAccount, UiAccountEncoding, UiDataSliceConfig},
20    solana_clap_utils::{
21        self,
22        compute_budget::{ComputeUnitLimit, compute_unit_price_arg},
23        fee_payer::{FEE_PAYER_ARG, fee_payer_arg},
24        hidden_unless_forced,
25        input_parsers::*,
26        input_validators::*,
27        keypair::*,
28        offline::{DUMP_TRANSACTION_MESSAGE, OfflineArgs, SIGN_ONLY_ARG},
29    },
30    solana_cli_output::{
31        CliProgram, CliProgramAccountType, CliProgramAuthority, CliProgramBuffer, CliProgramId,
32        CliUpgradeableBuffer, CliUpgradeableBuffers, CliUpgradeableProgram,
33        CliUpgradeableProgramClosed, CliUpgradeableProgramExtended, CliUpgradeablePrograms,
34        ReturnSignersConfig, return_signers_with_config,
35    },
36    solana_client::send_and_confirm_transactions_in_parallel::{
37        SendAndConfirmConfigV3, SendTransport, send_and_confirm_transactions_in_parallel_v3,
38    },
39    solana_commitment_config::CommitmentConfig,
40    solana_instruction::{Instruction, 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::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    verify(
3068        executable.get_text_bytes().1,
3069        executable.get_sbpf_version(),
3070        executable.get_loader().get_function_registry(),
3071    )
3072    .map_err(|err| format!("Verifier error: {err} (local pre-flight)").into())
3073}
3074
3075/// Turns an `EbpfError` from local ELF verification into a concise error
3076/// message and a remediation hint.
3077fn explain_elf_error(err: &EbpfError, program_data: &[u8], config: &Config) -> String {
3078    // `UnsupportedSBPFVersion` is special-cased here. Richer, per-field
3079    // diagnostics for malformed headers require changes to `sbpf::ElfError`;
3080    // tracked by https://github.com/anza-xyz/sbpf/issues/204.
3081    let EbpfError::ElfError(ElfError::UnsupportedSBPFVersion) = err else {
3082        return format!("{err} (local pre-flight)");
3083    };
3084
3085    fn sbpf_version_label(version: SBPFVersion) -> &'static str {
3086        match version {
3087            SBPFVersion::V0 => "v0",
3088            SBPFVersion::V1 => "v1",
3089            SBPFVersion::V2 => "v2",
3090            SBPFVersion::V3 => "v3",
3091            SBPFVersion::V4 => "v4",
3092            SBPFVersion::Reserved => "reserved",
3093        }
3094    }
3095
3096    let min = sbpf_version_label(*config.enabled_sbpf_versions.start());
3097    let max = sbpf_version_label(*config.enabled_sbpf_versions.end());
3098    match get_sbpf_version(program_data) {
3099        Ok(version) => {
3100            let detected = sbpf_version_label(version);
3101            format!(
3102                "program targets SBPF {detected}, which this CLI does not have enabled (enabled: \
3103                 {min}–{max}). Rebuild the program targeting {max}."
3104            )
3105        }
3106        Err(error) => format!("could not read SBPF version: {error} (local pre-flight)"),
3107    }
3108}
3109
3110async fn check_payer(
3111    rpc_client: &RpcClient,
3112    config: &CliConfig<'_>,
3113    fee_payer_pubkey: Pubkey,
3114    balance_needed: u64,
3115    initial_message: &Option<Message>,
3116    write_messages: &[Message],
3117    final_message: &Option<Message>,
3118) -> Result<(), Box<dyn std::error::Error>> {
3119    let mut fee = Saturating(0);
3120    if let Some(message) = initial_message {
3121        fee += rpc_client.get_fee_for_message(message).await?;
3122    }
3123    // Assume all write messages cost the same
3124    if let Some(message) = write_messages.first() {
3125        fee += rpc_client
3126            .get_fee_for_message(message)
3127            .await?
3128            .saturating_mul(write_messages.len() as u64);
3129    }
3130    if let Some(message) = final_message {
3131        fee += rpc_client.get_fee_for_message(message).await?;
3132    }
3133    check_account_for_spend_and_fee_with_commitment(
3134        rpc_client,
3135        &fee_payer_pubkey,
3136        balance_needed,
3137        fee.0,
3138        config.commitment,
3139    )
3140    .await?;
3141    Ok(())
3142}
3143
3144fn dedup_signers<'a>(signers: &[&'a dyn Signer]) -> Vec<&'a dyn Signer> {
3145    let mut seen = Vec::with_capacity(signers.len());
3146    signers
3147        .iter()
3148        .filter(|signer| {
3149            let pubkey = signer.pubkey();
3150            let is_new = !seen.contains(&pubkey);
3151            if is_new {
3152                seen.push(pubkey);
3153            }
3154            is_new
3155        })
3156        .copied()
3157        .collect()
3158}
3159
3160#[allow(clippy::too_many_arguments)]
3161async fn send_deploy_messages(
3162    rpc_client: Arc<RpcClient>,
3163    config: &CliConfig<'_>,
3164    initial_message: Option<Message>,
3165    mut write_messages: Vec<Message>,
3166    final_message: Option<Message>,
3167    fee_payer_signer: &dyn Signer,
3168    initial_signer: Option<&dyn Signer>,
3169    write_signer: Option<&dyn Signer>,
3170    final_signers: Option<&[&dyn Signer]>,
3171    max_sign_attempts: usize,
3172    use_rpc: bool,
3173    compute_unit_limit: &ComputeUnitLimit,
3174) -> Result<Option<Signature>, Box<dyn std::error::Error>> {
3175    if let Some(mut message) = initial_message {
3176        if let Some(initial_signer) = initial_signer {
3177            trace!("Preparing the required accounts");
3178            simulate_and_update_compute_unit_limit(compute_unit_limit, &rpc_client, &mut message)
3179                .await?;
3180            let mut initial_transaction = Transaction::new_unsigned(message.clone());
3181            let blockhash = rpc_client.get_latest_blockhash().await?;
3182
3183            // Most of the initial_transaction combinations require both the fee-payer and new program
3184            // account to sign the transaction. One (transfer) only requires the fee-payer signature.
3185            // This check is to ensure signing does not fail on a KeypairPubkeyMismatch error from an
3186            // extraneous signature.
3187            if message.header.num_required_signatures == 3 {
3188                initial_transaction.try_sign(
3189                    &[fee_payer_signer, initial_signer, write_signer.unwrap()],
3190                    blockhash,
3191                )?;
3192            } else if message.header.num_required_signatures == 2 {
3193                initial_transaction.try_sign(&[fee_payer_signer, initial_signer], blockhash)?;
3194            } else {
3195                initial_transaction.try_sign(&[fee_payer_signer], blockhash)?;
3196            }
3197            let result = rpc_client
3198                .send_and_confirm_transaction_with_spinner_and_config(
3199                    &initial_transaction,
3200                    config.commitment,
3201                    config.send_transaction_config,
3202                )
3203                .await;
3204            log_instruction_custom_error::<SystemError>(result, config)
3205                .map_err(|err| format!("Account allocation failed: {err}"))?;
3206        } else {
3207            return Err("Buffer account not created yet, must provide a key pair".into());
3208        }
3209    }
3210
3211    if !write_messages.is_empty()
3212        && let Some(write_signer) = write_signer
3213    {
3214        trace!("Writing program data");
3215
3216        // Simulate the first write message to get the number of compute units
3217        // consumed and then reuse that value as the compute unit limit for all
3218        // write messages.
3219        {
3220            let mut message = write_messages[0].clone();
3221            if let UpdateComputeUnitLimitResult::UpdatedInstructionIndex(ix_index) =
3222                simulate_and_update_compute_unit_limit(
3223                    compute_unit_limit,
3224                    &rpc_client,
3225                    &mut message,
3226                )
3227                .await?
3228            {
3229                for msg in &mut write_messages {
3230                    // Write messages are all assumed to be identical except
3231                    // the program data being written. But just in case that
3232                    // assumption is broken, assert that we are only ever
3233                    // changing the instruction data for a compute budget
3234                    // instruction.
3235                    assert_eq!(msg.program_id(ix_index), Some(&compute_budget::id()));
3236                    msg.instructions[ix_index]
3237                        .data
3238                        .clone_from(&message.instructions[ix_index].data);
3239                }
3240            }
3241
3242            // Holds the scheduler alive for the duration of the send.
3243            let _tpu_client;
3244            let cancel_token = CancellationToken::new();
3245            let transport = if use_rpc {
3246                SendTransport::Rpc(config.send_transaction_config)
3247            } else {
3248                let node_address_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) =
3259                    ClientBuilder::new(Box::new(node_address_service))
3260                        .cancel_token(cancel_token.clone())
3261                        .bind_socket(bind_socket)
3262                        .broadcaster(NonblockingBroadcaster)
3263                        .build()
3264                        .expect("Failed to build TPU client");
3265                _tpu_client = client;
3266                SendTransport::Tpu(transaction_sender)
3267            };
3268
3269            let versioned_write_messages = write_messages.into_iter().map(VersionedMessage::Legacy);
3270
3271            let transaction_errors = send_and_confirm_transactions_in_parallel_v3(
3272                rpc_client.clone(),
3273                transport,
3274                versioned_write_messages,
3275                &dedup_signers(&[fee_payer_signer, write_signer]),
3276                SendAndConfirmConfigV3 {
3277                    with_spinner: true,
3278                    max_sign_attempts: NonZeroUsize::new(max_sign_attempts)
3279                        .ok_or("--max-sign-attempts must be at least 1")?,
3280                    check_interval: CHECK_INTERVAL,
3281                    send_interval: SEND_INTERVAL,
3282                },
3283            )
3284            .await
3285            .map_err(|err| format!("Data writes to account failed: {err}"))?
3286            .into_iter()
3287            .flatten()
3288            .collect::<Vec<_>>();
3289
3290            if !transaction_errors.is_empty() {
3291                for transaction_error in &transaction_errors {
3292                    error!("{transaction_error:?}");
3293                }
3294                return Err(
3295                    format!("{} write transactions failed", transaction_errors.len()).into(),
3296                );
3297            }
3298        }
3299    }
3300
3301    if let Some(mut message) = final_message
3302        && let Some(final_signers) = final_signers
3303    {
3304        trace!("Deploying program");
3305
3306        simulate_and_update_compute_unit_limit(compute_unit_limit, &rpc_client, &mut message)
3307            .await?;
3308        let mut final_tx = Transaction::new_unsigned(message);
3309        let blockhash = rpc_client.get_latest_blockhash().await?;
3310        let mut signers = final_signers.to_vec();
3311        signers.push(fee_payer_signer);
3312        final_tx.try_sign(&signers, blockhash)?;
3313        let result = rpc_client
3314            .send_and_confirm_transaction_with_spinner_and_config(
3315                &final_tx,
3316                config.commitment,
3317                config.send_transaction_config,
3318            )
3319            .await
3320            .map_err(|e| format!("Deploying program failed: {e}"))?;
3321        return Ok(Some(result));
3322    }
3323
3324    Ok(None)
3325}
3326
3327fn create_ephemeral_keypair()
3328-> Result<(usize, bip39::Mnemonic, Keypair), Box<dyn std::error::Error>> {
3329    const WORDS: usize = 12;
3330    let mnemonic = Mnemonic::generate_in(Language::English, WORDS)?;
3331    let seed = mnemonic.to_seed("");
3332    let new_keypair = keypair_from_seed(&seed)?;
3333
3334    Ok((WORDS, mnemonic, new_keypair))
3335}
3336
3337fn report_ephemeral_mnemonic(words: usize, mnemonic: bip39::Mnemonic, ephemeral_pubkey: &Pubkey) {
3338    let phrase = mnemonic.to_string();
3339    let divider = String::from_utf8(vec![b'='; phrase.len()]).unwrap();
3340    eprintln!("{divider}\nRecover the intermediate account's ephemeral keypair file with");
3341    eprintln!("`solana-keygen recover` and the following {words}-word seed phrase:");
3342    eprintln!("{divider}\n{phrase}\n{divider}");
3343    eprintln!("To resume a deploy, pass the recovered keypair as the");
3344    eprintln!("[BUFFER_SIGNER] to `solana program deploy` or `solana program write-buffer'.");
3345    eprintln!("Or to recover the account's lamports, use:");
3346    eprintln!("{divider}\nsolana program close {ephemeral_pubkey}\n{divider}");
3347}
3348
3349async fn fetch_feature_set(
3350    rpc_client: &RpcClient,
3351) -> Result<FeatureSet, Box<dyn std::error::Error>> {
3352    let mut feature_set = FeatureSet::default();
3353    for feature_ids in FEATURE_NAMES
3354        .keys()
3355        .cloned()
3356        .collect::<Vec<Pubkey>>()
3357        .chunks(MAX_MULTIPLE_ACCOUNTS)
3358    {
3359        rpc_client
3360            .get_multiple_accounts(feature_ids)
3361            .await?
3362            .into_iter()
3363            .zip(feature_ids)
3364            .for_each(|(account, feature_id)| {
3365                let activation_slot = account.and_then(status_from_account);
3366
3367                if let Some(CliFeatureStatus::Active(slot)) = activation_slot {
3368                    feature_set.activate(feature_id, slot);
3369                }
3370            });
3371    }
3372
3373    Ok(feature_set)
3374}
3375
3376#[cfg(test)]
3377mod tests {
3378    use {
3379        super::*,
3380        crate::{
3381            clap_app::get_clap_app,
3382            cli::{parse_command, process_command},
3383        },
3384        serde_json::Value,
3385        solana_cli_output::OutputFormat,
3386        solana_hash::Hash,
3387        solana_keypair::write_keypair_file,
3388        solana_sbpf::elf_parser::consts::{
3389            EI_OSABI, ELFCLASS64, ELFDATA2LSB, ELFMAG, ELFOSABI_NONE, EV_CURRENT,
3390        },
3391    };
3392
3393    fn make_tmp_path(name: &str) -> String {
3394        let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
3395        let keypair = Keypair::new();
3396
3397        let path = format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey());
3398
3399        // whack any possible collision
3400        let _ignored = std::fs::remove_dir_all(&path);
3401        // whack any possible collision
3402        let _ignored = std::fs::remove_file(&path);
3403
3404        path
3405    }
3406
3407    #[test]
3408    #[allow(clippy::cognitive_complexity)]
3409    fn test_cli_parse_deploy() {
3410        let test_commands = get_clap_app("test", "desc", "version");
3411
3412        let default_keypair = Keypair::new();
3413        let keypair_file = make_tmp_path("keypair_file");
3414        write_keypair_file(&default_keypair, &keypair_file).unwrap();
3415        let default_signer = DefaultSigner::new("", &keypair_file);
3416
3417        let test_command = test_commands.clone().get_matches_from(vec![
3418            "test",
3419            "program",
3420            "deploy",
3421            "/Users/test/program.so",
3422        ]);
3423        assert_eq!(
3424            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3425            CliCommandInfo {
3426                command: CliCommand::Program(ProgramCliCommand::Deploy {
3427                    program_location: Some("/Users/test/program.so".to_string()),
3428                    fee_payer_signer_index: 0,
3429                    buffer_signer_index: None,
3430                    buffer_pubkey: None,
3431                    program_signer_index: None,
3432                    program_pubkey: None,
3433                    upgrade_authority_signer_index: 0,
3434                    is_final: false,
3435                    max_len: None,
3436                    skip_fee_check: false,
3437                    compute_unit_price: None,
3438                    max_sign_attempts: 5,
3439                    auto_extend: true,
3440                    use_rpc: false,
3441                    skip_feature_verification: false,
3442                }),
3443                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3444            }
3445        );
3446
3447        let test_command = test_commands.clone().get_matches_from(vec![
3448            "test",
3449            "program",
3450            "deploy",
3451            "/Users/test/program.so",
3452            "--max-len",
3453            "42",
3454        ]);
3455        assert_eq!(
3456            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3457            CliCommandInfo {
3458                command: CliCommand::Program(ProgramCliCommand::Deploy {
3459                    program_location: Some("/Users/test/program.so".to_string()),
3460                    fee_payer_signer_index: 0,
3461                    buffer_signer_index: None,
3462                    buffer_pubkey: None,
3463                    program_signer_index: None,
3464                    program_pubkey: None,
3465                    upgrade_authority_signer_index: 0,
3466                    is_final: false,
3467                    max_len: Some(42),
3468                    skip_fee_check: false,
3469                    compute_unit_price: None,
3470                    max_sign_attempts: 5,
3471                    auto_extend: true,
3472                    use_rpc: false,
3473                    skip_feature_verification: false,
3474                }),
3475                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3476            }
3477        );
3478
3479        let buffer_keypair = Keypair::new();
3480        let buffer_keypair_file = make_tmp_path("buffer_keypair_file");
3481        write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap();
3482        let test_command = test_commands.clone().get_matches_from(vec![
3483            "test",
3484            "program",
3485            "deploy",
3486            "--buffer",
3487            &buffer_keypair_file,
3488        ]);
3489        assert_eq!(
3490            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3491            CliCommandInfo {
3492                command: CliCommand::Program(ProgramCliCommand::Deploy {
3493                    program_location: None,
3494                    fee_payer_signer_index: 0,
3495                    buffer_signer_index: Some(1),
3496                    buffer_pubkey: Some(buffer_keypair.pubkey()),
3497                    program_signer_index: None,
3498                    program_pubkey: None,
3499                    upgrade_authority_signer_index: 0,
3500                    is_final: false,
3501                    max_len: None,
3502                    skip_fee_check: false,
3503                    compute_unit_price: None,
3504                    max_sign_attempts: 5,
3505                    auto_extend: true,
3506                    use_rpc: false,
3507                    skip_feature_verification: false,
3508                }),
3509                signers: vec![
3510                    Box::new(read_keypair_file(&keypair_file).unwrap()),
3511                    Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
3512                ],
3513            }
3514        );
3515
3516        let program_pubkey = Pubkey::new_unique();
3517        let test = test_commands.clone().get_matches_from(vec![
3518            "test",
3519            "program",
3520            "deploy",
3521            "/Users/test/program.so",
3522            "--program-id",
3523            &program_pubkey.to_string(),
3524        ]);
3525        assert_eq!(
3526            parse_command(&test, &default_signer, &mut None).unwrap(),
3527            CliCommandInfo {
3528                command: CliCommand::Program(ProgramCliCommand::Deploy {
3529                    program_location: Some("/Users/test/program.so".to_string()),
3530                    fee_payer_signer_index: 0,
3531                    buffer_signer_index: None,
3532                    buffer_pubkey: None,
3533                    program_signer_index: None,
3534                    program_pubkey: Some(program_pubkey),
3535                    upgrade_authority_signer_index: 0,
3536                    is_final: false,
3537                    max_len: None,
3538                    skip_fee_check: false,
3539                    compute_unit_price: None,
3540                    max_sign_attempts: 5,
3541                    auto_extend: true,
3542                    use_rpc: false,
3543                    skip_feature_verification: false,
3544                }),
3545                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3546            }
3547        );
3548
3549        let program_keypair = Keypair::new();
3550        let program_keypair_file = make_tmp_path("program_keypair_file");
3551        write_keypair_file(&program_keypair, &program_keypair_file).unwrap();
3552        let test = test_commands.clone().get_matches_from(vec![
3553            "test",
3554            "program",
3555            "deploy",
3556            "/Users/test/program.so",
3557            "--program-id",
3558            &program_keypair_file,
3559        ]);
3560        assert_eq!(
3561            parse_command(&test, &default_signer, &mut None).unwrap(),
3562            CliCommandInfo {
3563                command: CliCommand::Program(ProgramCliCommand::Deploy {
3564                    program_location: Some("/Users/test/program.so".to_string()),
3565                    fee_payer_signer_index: 0,
3566                    buffer_signer_index: None,
3567                    buffer_pubkey: None,
3568                    program_signer_index: Some(1),
3569                    program_pubkey: Some(program_keypair.pubkey()),
3570                    upgrade_authority_signer_index: 0,
3571                    is_final: false,
3572                    max_len: None,
3573                    skip_fee_check: false,
3574                    compute_unit_price: None,
3575                    max_sign_attempts: 5,
3576                    auto_extend: true,
3577                    use_rpc: false,
3578                    skip_feature_verification: false,
3579                }),
3580                signers: vec![
3581                    Box::new(read_keypair_file(&keypair_file).unwrap()),
3582                    Box::new(read_keypair_file(&program_keypair_file).unwrap()),
3583                ],
3584            }
3585        );
3586
3587        let authority_keypair = Keypair::new();
3588        let authority_keypair_file = make_tmp_path("authority_keypair_file");
3589        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
3590        let test_command = test_commands.clone().get_matches_from(vec![
3591            "test",
3592            "program",
3593            "deploy",
3594            "/Users/test/program.so",
3595            "--upgrade-authority",
3596            &authority_keypair_file,
3597        ]);
3598        assert_eq!(
3599            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3600            CliCommandInfo {
3601                command: CliCommand::Program(ProgramCliCommand::Deploy {
3602                    program_location: Some("/Users/test/program.so".to_string()),
3603                    fee_payer_signer_index: 0,
3604                    buffer_signer_index: None,
3605                    buffer_pubkey: None,
3606                    program_signer_index: None,
3607                    program_pubkey: None,
3608                    upgrade_authority_signer_index: 1,
3609                    is_final: false,
3610                    max_len: None,
3611                    skip_fee_check: false,
3612                    compute_unit_price: None,
3613                    max_sign_attempts: 5,
3614                    auto_extend: true,
3615                    use_rpc: false,
3616                    skip_feature_verification: false,
3617                }),
3618                signers: vec![
3619                    Box::new(read_keypair_file(&keypair_file).unwrap()),
3620                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3621                ],
3622            }
3623        );
3624
3625        let test_command = test_commands.clone().get_matches_from(vec![
3626            "test",
3627            "program",
3628            "deploy",
3629            "/Users/test/program.so",
3630            "--final",
3631        ]);
3632        assert_eq!(
3633            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3634            CliCommandInfo {
3635                command: CliCommand::Program(ProgramCliCommand::Deploy {
3636                    program_location: Some("/Users/test/program.so".to_string()),
3637                    fee_payer_signer_index: 0,
3638                    buffer_signer_index: None,
3639                    buffer_pubkey: None,
3640                    program_signer_index: None,
3641                    program_pubkey: None,
3642                    upgrade_authority_signer_index: 0,
3643                    is_final: true,
3644                    max_len: None,
3645                    skip_fee_check: false,
3646                    compute_unit_price: None,
3647                    max_sign_attempts: 5,
3648                    auto_extend: true,
3649                    use_rpc: false,
3650                    skip_feature_verification: false,
3651                }),
3652                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3653            }
3654        );
3655
3656        let test_command = test_commands.clone().get_matches_from(vec![
3657            "test",
3658            "program",
3659            "deploy",
3660            "/Users/test/program.so",
3661            "--max-sign-attempts",
3662            "1",
3663        ]);
3664        assert_eq!(
3665            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3666            CliCommandInfo {
3667                command: CliCommand::Program(ProgramCliCommand::Deploy {
3668                    program_location: Some("/Users/test/program.so".to_string()),
3669                    fee_payer_signer_index: 0,
3670                    buffer_signer_index: None,
3671                    buffer_pubkey: None,
3672                    program_signer_index: None,
3673                    program_pubkey: None,
3674                    upgrade_authority_signer_index: 0,
3675                    is_final: false,
3676                    max_len: None,
3677                    skip_fee_check: false,
3678                    compute_unit_price: None,
3679                    max_sign_attempts: 1,
3680                    auto_extend: true,
3681                    use_rpc: false,
3682                    skip_feature_verification: false,
3683                }),
3684                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3685            }
3686        );
3687
3688        let test_command = test_commands.clone().get_matches_from(vec![
3689            "test",
3690            "program",
3691            "deploy",
3692            "/Users/test/program.so",
3693            "--use-rpc",
3694        ]);
3695        assert_eq!(
3696            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3697            CliCommandInfo {
3698                command: CliCommand::Program(ProgramCliCommand::Deploy {
3699                    program_location: Some("/Users/test/program.so".to_string()),
3700                    fee_payer_signer_index: 0,
3701                    buffer_signer_index: None,
3702                    buffer_pubkey: None,
3703                    program_signer_index: None,
3704                    program_pubkey: None,
3705                    upgrade_authority_signer_index: 0,
3706                    is_final: false,
3707                    max_len: None,
3708                    skip_fee_check: false,
3709                    compute_unit_price: None,
3710                    max_sign_attempts: 5,
3711                    auto_extend: true,
3712                    use_rpc: true,
3713                    skip_feature_verification: false,
3714                }),
3715                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3716            }
3717        );
3718
3719        let test_command = test_commands.clone().get_matches_from(vec![
3720            "test",
3721            "program",
3722            "deploy",
3723            "/Users/test/program.so",
3724            "--skip-feature-verify",
3725        ]);
3726        assert_eq!(
3727            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3728            CliCommandInfo {
3729                command: CliCommand::Program(ProgramCliCommand::Deploy {
3730                    program_location: Some("/Users/test/program.so".to_string()),
3731                    fee_payer_signer_index: 0,
3732                    buffer_signer_index: None,
3733                    buffer_pubkey: None,
3734                    program_signer_index: None,
3735                    program_pubkey: None,
3736                    upgrade_authority_signer_index: 0,
3737                    is_final: false,
3738                    max_len: None,
3739                    skip_fee_check: false,
3740                    compute_unit_price: None,
3741                    max_sign_attempts: 5,
3742                    auto_extend: true,
3743                    use_rpc: false,
3744                    skip_feature_verification: true,
3745                }),
3746                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3747            }
3748        );
3749    }
3750
3751    #[test]
3752    fn test_cli_parse_upgrade() {
3753        let test_commands = get_clap_app("test", "desc", "version");
3754
3755        let default_keypair = Keypair::new();
3756        let keypair_file = make_tmp_path("keypair_file");
3757        write_keypair_file(&default_keypair, &keypair_file).unwrap();
3758        let default_signer = DefaultSigner::new("", &keypair_file);
3759
3760        let program_key = Pubkey::new_unique();
3761        let buffer_key = Pubkey::new_unique();
3762        let test_command = test_commands.clone().get_matches_from(vec![
3763            "test",
3764            "program",
3765            "upgrade",
3766            format!("{buffer_key}").as_str(),
3767            format!("{program_key}").as_str(),
3768            "--skip-feature-verify",
3769        ]);
3770        assert_eq!(
3771            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3772            CliCommandInfo {
3773                command: CliCommand::Program(ProgramCliCommand::Upgrade {
3774                    fee_payer_signer_index: 0,
3775                    program_pubkey: program_key,
3776                    buffer_pubkey: buffer_key,
3777                    upgrade_authority_signer_index: 0,
3778                    sign_only: false,
3779                    dump_transaction_message: false,
3780                    blockhash_query: BlockhashQuery::default(),
3781                    skip_feature_verification: true,
3782                }),
3783                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3784            }
3785        );
3786    }
3787
3788    #[test]
3789    #[allow(clippy::cognitive_complexity)]
3790    fn test_cli_parse_write_buffer() {
3791        let test_commands = get_clap_app("test", "desc", "version");
3792
3793        let default_keypair = Keypair::new();
3794        let keypair_file = make_tmp_path("keypair_file");
3795        write_keypair_file(&default_keypair, &keypair_file).unwrap();
3796        let default_signer = DefaultSigner::new("", &keypair_file);
3797
3798        // defaults
3799        let test_command = test_commands.clone().get_matches_from(vec![
3800            "test",
3801            "program",
3802            "write-buffer",
3803            "/Users/test/program.so",
3804        ]);
3805        assert_eq!(
3806            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3807            CliCommandInfo {
3808                command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
3809                    program_location: "/Users/test/program.so".to_string(),
3810                    fee_payer_signer_index: 0,
3811                    buffer_signer_index: None,
3812                    buffer_pubkey: None,
3813                    buffer_authority_signer_index: 0,
3814                    max_len: None,
3815                    skip_fee_check: false,
3816                    compute_unit_price: None,
3817                    max_sign_attempts: 5,
3818                    use_rpc: false,
3819                    skip_feature_verification: false,
3820                }),
3821                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3822            }
3823        );
3824
3825        // specify max len
3826        let test_command = test_commands.clone().get_matches_from(vec![
3827            "test",
3828            "program",
3829            "write-buffer",
3830            "/Users/test/program.so",
3831            "--max-len",
3832            "42",
3833        ]);
3834        assert_eq!(
3835            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3836            CliCommandInfo {
3837                command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
3838                    program_location: "/Users/test/program.so".to_string(),
3839                    fee_payer_signer_index: 0,
3840                    buffer_signer_index: None,
3841                    buffer_pubkey: None,
3842                    buffer_authority_signer_index: 0,
3843                    max_len: Some(42),
3844                    skip_fee_check: false,
3845                    compute_unit_price: None,
3846                    max_sign_attempts: 5,
3847                    use_rpc: false,
3848                    skip_feature_verification: false,
3849                }),
3850                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3851            }
3852        );
3853
3854        // specify buffer
3855        let buffer_keypair = Keypair::new();
3856        let buffer_keypair_file = make_tmp_path("buffer_keypair_file");
3857        write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap();
3858        let test_command = test_commands.clone().get_matches_from(vec![
3859            "test",
3860            "program",
3861            "write-buffer",
3862            "/Users/test/program.so",
3863            "--buffer",
3864            &buffer_keypair_file,
3865        ]);
3866        assert_eq!(
3867            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3868            CliCommandInfo {
3869                command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
3870                    program_location: "/Users/test/program.so".to_string(),
3871                    fee_payer_signer_index: 0,
3872                    buffer_signer_index: Some(1),
3873                    buffer_pubkey: Some(buffer_keypair.pubkey()),
3874                    buffer_authority_signer_index: 0,
3875                    max_len: None,
3876                    skip_fee_check: false,
3877                    compute_unit_price: None,
3878                    max_sign_attempts: 5,
3879                    use_rpc: false,
3880                    skip_feature_verification: false,
3881                }),
3882                signers: vec![
3883                    Box::new(read_keypair_file(&keypair_file).unwrap()),
3884                    Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
3885                ],
3886            }
3887        );
3888
3889        // specify authority
3890        let authority_keypair = Keypair::new();
3891        let authority_keypair_file = make_tmp_path("authority_keypair_file");
3892        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
3893        let test_command = test_commands.clone().get_matches_from(vec![
3894            "test",
3895            "program",
3896            "write-buffer",
3897            "/Users/test/program.so",
3898            "--buffer-authority",
3899            &authority_keypair_file,
3900        ]);
3901        assert_eq!(
3902            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3903            CliCommandInfo {
3904                command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
3905                    program_location: "/Users/test/program.so".to_string(),
3906                    fee_payer_signer_index: 0,
3907                    buffer_signer_index: None,
3908                    buffer_pubkey: None,
3909                    buffer_authority_signer_index: 1,
3910                    max_len: None,
3911                    skip_fee_check: false,
3912                    compute_unit_price: None,
3913                    max_sign_attempts: 5,
3914                    use_rpc: false,
3915                    skip_feature_verification: false,
3916                }),
3917                signers: vec![
3918                    Box::new(read_keypair_file(&keypair_file).unwrap()),
3919                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3920                ],
3921            }
3922        );
3923
3924        // specify both buffer and authority
3925        let buffer_keypair = Keypair::new();
3926        let buffer_keypair_file = make_tmp_path("buffer_keypair_file");
3927        write_keypair_file(&buffer_keypair, &buffer_keypair_file).unwrap();
3928        let authority_keypair = Keypair::new();
3929        let authority_keypair_file = make_tmp_path("authority_keypair_file");
3930        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
3931        let test_command = test_commands.clone().get_matches_from(vec![
3932            "test",
3933            "program",
3934            "write-buffer",
3935            "/Users/test/program.so",
3936            "--buffer",
3937            &buffer_keypair_file,
3938            "--buffer-authority",
3939            &authority_keypair_file,
3940        ]);
3941        assert_eq!(
3942            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3943            CliCommandInfo {
3944                command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
3945                    program_location: "/Users/test/program.so".to_string(),
3946                    fee_payer_signer_index: 0,
3947                    buffer_signer_index: Some(1),
3948                    buffer_pubkey: Some(buffer_keypair.pubkey()),
3949                    buffer_authority_signer_index: 2,
3950                    max_len: None,
3951                    skip_fee_check: false,
3952                    compute_unit_price: None,
3953                    max_sign_attempts: 5,
3954                    use_rpc: false,
3955                    skip_feature_verification: false,
3956                }),
3957                signers: vec![
3958                    Box::new(read_keypair_file(&keypair_file).unwrap()),
3959                    Box::new(read_keypair_file(&buffer_keypair_file).unwrap()),
3960                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3961                ],
3962            }
3963        );
3964
3965        // specify max sign attempts
3966        let test_command = test_commands.clone().get_matches_from(vec![
3967            "test",
3968            "program",
3969            "write-buffer",
3970            "/Users/test/program.so",
3971            "--max-sign-attempts",
3972            "10",
3973        ]);
3974        assert_eq!(
3975            parse_command(&test_command, &default_signer, &mut None).unwrap(),
3976            CliCommandInfo {
3977                command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
3978                    program_location: "/Users/test/program.so".to_string(),
3979                    fee_payer_signer_index: 0,
3980                    buffer_signer_index: None,
3981                    buffer_pubkey: None,
3982                    buffer_authority_signer_index: 0,
3983                    max_len: None,
3984                    skip_fee_check: false,
3985                    compute_unit_price: None,
3986                    max_sign_attempts: 10,
3987                    use_rpc: false,
3988                    skip_feature_verification: false
3989                }),
3990                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
3991            }
3992        );
3993
3994        // skip feature verification
3995        let test_command = test_commands.clone().get_matches_from(vec![
3996            "test",
3997            "program",
3998            "write-buffer",
3999            "/Users/test/program.so",
4000            "--skip-feature-verify",
4001        ]);
4002        assert_eq!(
4003            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4004            CliCommandInfo {
4005                command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
4006                    program_location: "/Users/test/program.so".to_string(),
4007                    fee_payer_signer_index: 0,
4008                    buffer_signer_index: None,
4009                    buffer_pubkey: None,
4010                    buffer_authority_signer_index: 0,
4011                    max_len: None,
4012                    skip_fee_check: false,
4013                    compute_unit_price: None,
4014                    max_sign_attempts: 5,
4015                    use_rpc: false,
4016                    skip_feature_verification: true,
4017                }),
4018                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4019            }
4020        );
4021    }
4022
4023    #[test]
4024    #[allow(clippy::cognitive_complexity)]
4025    fn test_cli_parse_set_upgrade_authority() {
4026        let test_commands = get_clap_app("test", "desc", "version");
4027
4028        let default_keypair = Keypair::new();
4029        let keypair_file = make_tmp_path("keypair_file");
4030        write_keypair_file(&default_keypair, &keypair_file).unwrap();
4031        let default_signer = DefaultSigner::new("", &keypair_file);
4032
4033        let program_pubkey = Pubkey::new_unique();
4034        let new_authority_pubkey = Pubkey::new_unique();
4035        let blockhash = Hash::new_unique();
4036
4037        let test_command = test_commands.clone().get_matches_from(vec![
4038            "test",
4039            "program",
4040            "set-upgrade-authority",
4041            &program_pubkey.to_string(),
4042            "--new-upgrade-authority",
4043            &new_authority_pubkey.to_string(),
4044            "--skip-new-upgrade-authority-signer-check",
4045            "--sign-only",
4046            "--dump-transaction-message",
4047            "--blockhash",
4048            blockhash.to_string().as_str(),
4049        ]);
4050        assert_eq!(
4051            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4052            CliCommandInfo {
4053                command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority {
4054                    program_pubkey,
4055                    upgrade_authority_index: Some(0),
4056                    new_upgrade_authority: Some(new_authority_pubkey),
4057                    sign_only: true,
4058                    dump_transaction_message: true,
4059                    blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
4060                }),
4061                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4062            }
4063        );
4064
4065        let program_pubkey = Pubkey::new_unique();
4066        let new_authority_pubkey = Keypair::new();
4067        let new_authority_pubkey_file = make_tmp_path("authority_keypair_file");
4068        write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap();
4069        let test_command = test_commands.clone().get_matches_from(vec![
4070            "test",
4071            "program",
4072            "set-upgrade-authority",
4073            &program_pubkey.to_string(),
4074            "--new-upgrade-authority",
4075            &new_authority_pubkey_file,
4076            "--skip-new-upgrade-authority-signer-check",
4077        ]);
4078        assert_eq!(
4079            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4080            CliCommandInfo {
4081                command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority {
4082                    program_pubkey,
4083                    upgrade_authority_index: Some(0),
4084                    new_upgrade_authority: Some(new_authority_pubkey.pubkey()),
4085                    sign_only: false,
4086                    dump_transaction_message: false,
4087                    blockhash_query: BlockhashQuery::default(),
4088                }),
4089                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4090            }
4091        );
4092
4093        let blockhash = Hash::new_unique();
4094        let program_pubkey = Pubkey::new_unique();
4095        let new_authority_pubkey = Keypair::new();
4096        let new_authority_pubkey_file = make_tmp_path("authority_keypair_file");
4097        write_keypair_file(&new_authority_pubkey, &new_authority_pubkey_file).unwrap();
4098        let test_command = test_commands.clone().get_matches_from(vec![
4099            "test",
4100            "program",
4101            "set-upgrade-authority",
4102            &program_pubkey.to_string(),
4103            "--new-upgrade-authority",
4104            &new_authority_pubkey_file,
4105            "--sign-only",
4106            "--dump-transaction-message",
4107            "--blockhash",
4108            blockhash.to_string().as_str(),
4109        ]);
4110        assert_eq!(
4111            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4112            CliCommandInfo {
4113                command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthorityChecked {
4114                    program_pubkey,
4115                    upgrade_authority_index: 0,
4116                    new_upgrade_authority_index: 1,
4117                    sign_only: true,
4118                    dump_transaction_message: true,
4119                    blockhash_query: BlockhashQuery::new(Some(blockhash), true, None),
4120                }),
4121                signers: vec![
4122                    Box::new(read_keypair_file(&keypair_file).unwrap()),
4123                    Box::new(read_keypair_file(&new_authority_pubkey_file).unwrap()),
4124                ],
4125            }
4126        );
4127
4128        let program_pubkey = Pubkey::new_unique();
4129        let new_authority_pubkey = Keypair::new();
4130        let new_authority_pubkey_file = make_tmp_path("authority_keypair_file");
4131        write_keypair_file(&new_authority_pubkey, new_authority_pubkey_file).unwrap();
4132        let test_command = test_commands.clone().get_matches_from(vec![
4133            "test",
4134            "program",
4135            "set-upgrade-authority",
4136            &program_pubkey.to_string(),
4137            "--final",
4138        ]);
4139        assert_eq!(
4140            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4141            CliCommandInfo {
4142                command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority {
4143                    program_pubkey,
4144                    upgrade_authority_index: Some(0),
4145                    new_upgrade_authority: None,
4146                    sign_only: false,
4147                    dump_transaction_message: false,
4148                    blockhash_query: BlockhashQuery::default(),
4149                }),
4150                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4151            }
4152        );
4153
4154        let program_pubkey = Pubkey::new_unique();
4155        let authority = Keypair::new();
4156        let authority_keypair_file = make_tmp_path("authority_keypair_file");
4157        write_keypair_file(&authority, &authority_keypair_file).unwrap();
4158        let test_command = test_commands.clone().get_matches_from(vec![
4159            "test",
4160            "program",
4161            "set-upgrade-authority",
4162            &program_pubkey.to_string(),
4163            "--upgrade-authority",
4164            &authority_keypair_file,
4165            "--final",
4166        ]);
4167        assert_eq!(
4168            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4169            CliCommandInfo {
4170                command: CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority {
4171                    program_pubkey,
4172                    upgrade_authority_index: Some(1),
4173                    new_upgrade_authority: None,
4174                    sign_only: false,
4175                    dump_transaction_message: false,
4176                    blockhash_query: BlockhashQuery::default(),
4177                }),
4178                signers: vec![
4179                    Box::new(read_keypair_file(&keypair_file).unwrap()),
4180                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
4181                ],
4182            }
4183        );
4184    }
4185
4186    #[test]
4187    #[allow(clippy::cognitive_complexity)]
4188    fn test_cli_parse_set_buffer_authority() {
4189        let test_commands = get_clap_app("test", "desc", "version");
4190
4191        let default_keypair = Keypair::new();
4192        let keypair_file = make_tmp_path("keypair_file");
4193        write_keypair_file(&default_keypair, &keypair_file).unwrap();
4194        let default_signer = DefaultSigner::new("", &keypair_file);
4195
4196        let buffer_pubkey = Pubkey::new_unique();
4197        let new_authority_pubkey = Pubkey::new_unique();
4198        let test_command = test_commands.clone().get_matches_from(vec![
4199            "test",
4200            "program",
4201            "set-buffer-authority",
4202            &buffer_pubkey.to_string(),
4203            "--new-buffer-authority",
4204            &new_authority_pubkey.to_string(),
4205        ]);
4206        assert_eq!(
4207            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4208            CliCommandInfo {
4209                command: CliCommand::Program(ProgramCliCommand::SetBufferAuthority {
4210                    buffer_pubkey,
4211                    buffer_authority_index: Some(0),
4212                    new_buffer_authority: new_authority_pubkey,
4213                }),
4214                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4215            }
4216        );
4217
4218        let buffer_pubkey = Pubkey::new_unique();
4219        let new_authority_keypair = Keypair::new();
4220        let new_authority_keypair_file = make_tmp_path("authority_keypair_file");
4221        write_keypair_file(&new_authority_keypair, &new_authority_keypair_file).unwrap();
4222        let test_command = test_commands.clone().get_matches_from(vec![
4223            "test",
4224            "program",
4225            "set-buffer-authority",
4226            &buffer_pubkey.to_string(),
4227            "--new-buffer-authority",
4228            &new_authority_keypair_file,
4229        ]);
4230        assert_eq!(
4231            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4232            CliCommandInfo {
4233                command: CliCommand::Program(ProgramCliCommand::SetBufferAuthority {
4234                    buffer_pubkey,
4235                    buffer_authority_index: Some(0),
4236                    new_buffer_authority: new_authority_keypair.pubkey(),
4237                }),
4238                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4239            }
4240        );
4241    }
4242
4243    #[test]
4244    #[allow(clippy::cognitive_complexity)]
4245    fn test_cli_parse_show() {
4246        let test_commands = get_clap_app("test", "desc", "version");
4247
4248        let default_keypair = Keypair::new();
4249        let keypair_file = make_tmp_path("keypair_file");
4250        write_keypair_file(&default_keypair, &keypair_file).unwrap();
4251        let default_signer = DefaultSigner::new("", &keypair_file);
4252
4253        // defaults
4254        let buffer_pubkey = Pubkey::new_unique();
4255        let authority_keypair = Keypair::new();
4256        let authority_keypair_file = make_tmp_path("authority_keypair_file");
4257        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
4258
4259        let test_command = test_commands.clone().get_matches_from(vec![
4260            "test",
4261            "program",
4262            "show",
4263            &buffer_pubkey.to_string(),
4264        ]);
4265        assert_eq!(
4266            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4267            CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Show {
4268                account_pubkey: Some(buffer_pubkey),
4269                authority_pubkey: default_keypair.pubkey(),
4270                get_programs: false,
4271                get_buffers: false,
4272                all: false,
4273                use_lamports_unit: false,
4274            }))
4275        );
4276
4277        let test_command = test_commands.clone().get_matches_from(vec![
4278            "test",
4279            "program",
4280            "show",
4281            "--programs",
4282            "--all",
4283            "--lamports",
4284        ]);
4285        assert_eq!(
4286            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4287            CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Show {
4288                account_pubkey: None,
4289                authority_pubkey: default_keypair.pubkey(),
4290                get_programs: true,
4291                get_buffers: false,
4292                all: true,
4293                use_lamports_unit: true,
4294            }))
4295        );
4296
4297        let test_command = test_commands.clone().get_matches_from(vec![
4298            "test",
4299            "program",
4300            "show",
4301            "--buffers",
4302            "--all",
4303            "--lamports",
4304        ]);
4305        assert_eq!(
4306            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4307            CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Show {
4308                account_pubkey: None,
4309                authority_pubkey: default_keypair.pubkey(),
4310                get_programs: false,
4311                get_buffers: true,
4312                all: true,
4313                use_lamports_unit: true,
4314            }))
4315        );
4316
4317        let test_command = test_commands.clone().get_matches_from(vec![
4318            "test",
4319            "program",
4320            "show",
4321            "--buffers",
4322            "--buffer-authority",
4323            &authority_keypair.pubkey().to_string(),
4324        ]);
4325        assert_eq!(
4326            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4327            CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Show {
4328                account_pubkey: None,
4329                authority_pubkey: authority_keypair.pubkey(),
4330                get_programs: false,
4331                get_buffers: true,
4332                all: false,
4333                use_lamports_unit: false,
4334            }))
4335        );
4336
4337        let test_command = test_commands.clone().get_matches_from(vec![
4338            "test",
4339            "program",
4340            "show",
4341            "--buffers",
4342            "--buffer-authority",
4343            &authority_keypair_file,
4344        ]);
4345        assert_eq!(
4346            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4347            CliCommandInfo::without_signers(CliCommand::Program(ProgramCliCommand::Show {
4348                account_pubkey: None,
4349                authority_pubkey: authority_keypair.pubkey(),
4350                get_programs: false,
4351                get_buffers: true,
4352                all: false,
4353                use_lamports_unit: false,
4354            }))
4355        );
4356    }
4357
4358    #[test]
4359    #[allow(clippy::cognitive_complexity)]
4360    fn test_cli_parse_close() {
4361        let test_commands = get_clap_app("test", "desc", "version");
4362
4363        let default_keypair = Keypair::new();
4364        let keypair_file = make_tmp_path("keypair_file");
4365        write_keypair_file(&default_keypair, &keypair_file).unwrap();
4366        let default_signer = DefaultSigner::new("", &keypair_file);
4367
4368        // defaults
4369        let buffer_pubkey = Pubkey::new_unique();
4370        let recipient_pubkey = Pubkey::new_unique();
4371        let authority_keypair = Keypair::new();
4372        let authority_keypair_file = make_tmp_path("authority_keypair_file");
4373
4374        let test_command = test_commands.clone().get_matches_from(vec![
4375            "test",
4376            "program",
4377            "close",
4378            &buffer_pubkey.to_string(),
4379        ]);
4380        assert_eq!(
4381            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4382            CliCommandInfo {
4383                command: CliCommand::Program(ProgramCliCommand::Close {
4384                    account_pubkey: Some(buffer_pubkey),
4385                    recipient_pubkey: default_keypair.pubkey(),
4386                    authority_index: 0,
4387                    use_lamports_unit: false,
4388                    bypass_warning: false,
4389                }),
4390                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4391            }
4392        );
4393
4394        // with bypass-warning
4395        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
4396        let test_command = test_commands.clone().get_matches_from(vec![
4397            "test",
4398            "program",
4399            "close",
4400            &buffer_pubkey.to_string(),
4401            "--bypass-warning",
4402        ]);
4403        assert_eq!(
4404            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4405            CliCommandInfo {
4406                command: CliCommand::Program(ProgramCliCommand::Close {
4407                    account_pubkey: Some(buffer_pubkey),
4408                    recipient_pubkey: default_keypair.pubkey(),
4409                    authority_index: 0,
4410                    use_lamports_unit: false,
4411                    bypass_warning: true,
4412                }),
4413                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4414            }
4415        );
4416
4417        // with authority
4418        write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap();
4419        let test_command = test_commands.clone().get_matches_from(vec![
4420            "test",
4421            "program",
4422            "close",
4423            &buffer_pubkey.to_string(),
4424            "--buffer-authority",
4425            &authority_keypair_file,
4426        ]);
4427        assert_eq!(
4428            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4429            CliCommandInfo {
4430                command: CliCommand::Program(ProgramCliCommand::Close {
4431                    account_pubkey: Some(buffer_pubkey),
4432                    recipient_pubkey: default_keypair.pubkey(),
4433                    authority_index: 1,
4434                    use_lamports_unit: false,
4435                    bypass_warning: false,
4436                }),
4437                signers: vec![
4438                    Box::new(read_keypair_file(&keypair_file).unwrap()),
4439                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
4440                ],
4441            }
4442        );
4443
4444        // with recipient
4445        let test_command = test_commands.clone().get_matches_from(vec![
4446            "test",
4447            "program",
4448            "close",
4449            &buffer_pubkey.to_string(),
4450            "--recipient",
4451            &recipient_pubkey.to_string(),
4452        ]);
4453        assert_eq!(
4454            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4455            CliCommandInfo {
4456                command: CliCommand::Program(ProgramCliCommand::Close {
4457                    account_pubkey: Some(buffer_pubkey),
4458                    recipient_pubkey,
4459                    authority_index: 0,
4460                    use_lamports_unit: false,
4461                    bypass_warning: false,
4462                }),
4463                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap()),],
4464            }
4465        );
4466
4467        // --buffers and lamports
4468        let test_command = test_commands.clone().get_matches_from(vec![
4469            "test",
4470            "program",
4471            "close",
4472            "--buffers",
4473            "--lamports",
4474        ]);
4475        assert_eq!(
4476            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4477            CliCommandInfo {
4478                command: CliCommand::Program(ProgramCliCommand::Close {
4479                    account_pubkey: None,
4480                    recipient_pubkey: default_keypair.pubkey(),
4481                    authority_index: 0,
4482                    use_lamports_unit: true,
4483                    bypass_warning: false,
4484                }),
4485                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap()),],
4486            }
4487        );
4488    }
4489
4490    #[test]
4491    fn test_cli_parse_extend_program() {
4492        let test_commands = get_clap_app("test", "desc", "version");
4493
4494        let default_keypair = Keypair::new();
4495        let keypair_file = make_tmp_path("keypair_file");
4496        write_keypair_file(&default_keypair, &keypair_file).unwrap();
4497        let default_signer = DefaultSigner::new("", &keypair_file);
4498
4499        // defaults
4500        let program_pubkey = Pubkey::new_unique();
4501        let additional_bytes = 100;
4502
4503        let test_command = test_commands.clone().get_matches_from(vec![
4504            "test",
4505            "program",
4506            "extend",
4507            &program_pubkey.to_string(),
4508            &additional_bytes.to_string(),
4509        ]);
4510        assert_eq!(
4511            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4512            CliCommandInfo {
4513                command: CliCommand::Program(ProgramCliCommand::ExtendProgram {
4514                    program_pubkey,
4515                    payer_signer_index: 0,
4516                    additional_bytes
4517                }),
4518                signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
4519            }
4520        );
4521
4522        // with payer
4523        let payer_keypair = Keypair::new();
4524        let payer_keypair_file = make_tmp_path("payer_keypair_file");
4525        write_keypair_file(&payer_keypair, &payer_keypair_file).unwrap();
4526        let test_command = test_commands.clone().get_matches_from(vec![
4527            "test",
4528            "program",
4529            "extend",
4530            &program_pubkey.to_string(),
4531            &additional_bytes.to_string(),
4532            "--payer",
4533            &payer_keypair_file,
4534        ]);
4535        assert_eq!(
4536            parse_command(&test_command, &default_signer, &mut None).unwrap(),
4537            CliCommandInfo {
4538                command: CliCommand::Program(ProgramCliCommand::ExtendProgram {
4539                    program_pubkey,
4540                    payer_signer_index: 1,
4541                    additional_bytes
4542                }),
4543                signers: vec![
4544                    Box::new(read_keypair_file(&keypair_file).unwrap()),
4545                    Box::new(read_keypair_file(&payer_keypair_file).unwrap()),
4546                ],
4547            }
4548        );
4549    }
4550
4551    #[tokio::test]
4552    async fn test_cli_keypair_file() {
4553        agave_logger::setup();
4554
4555        let default_keypair = Keypair::new();
4556        let program_pubkey = Keypair::new();
4557        let deploy_path = make_tmp_path("deploy");
4558        let mut program_location = PathBuf::from(deploy_path.clone());
4559        program_location.push("noop");
4560        program_location.set_extension("so");
4561        let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
4562        pathbuf.push("tests");
4563        pathbuf.push("fixtures");
4564        pathbuf.push("noop");
4565        pathbuf.set_extension("so");
4566        let program_keypair_location = program_location.with_file_name("noop-keypair.json");
4567        std::fs::create_dir_all(deploy_path).unwrap();
4568        std::fs::copy(pathbuf, program_location.as_os_str()).unwrap();
4569        write_keypair_file(&program_pubkey, program_keypair_location).unwrap();
4570
4571        let config = CliConfig {
4572            rpc_client: Some(Arc::new(RpcClient::new_mock("".to_string()))),
4573            command: CliCommand::Program(ProgramCliCommand::Deploy {
4574                program_location: Some(program_location.to_str().unwrap().to_string()),
4575                fee_payer_signer_index: 0,
4576                buffer_signer_index: None,
4577                buffer_pubkey: None,
4578                program_signer_index: None,
4579                program_pubkey: None,
4580                upgrade_authority_signer_index: 0,
4581                is_final: false,
4582                max_len: None,
4583                skip_fee_check: false,
4584                compute_unit_price: None,
4585                max_sign_attempts: 5,
4586                auto_extend: true,
4587                use_rpc: false,
4588                skip_feature_verification: true,
4589            }),
4590            signers: vec![&default_keypair],
4591            output_format: OutputFormat::JsonCompact,
4592            ..CliConfig::default()
4593        };
4594
4595        let result = process_command(&config).await;
4596        let json: Value = serde_json::from_str(&result.unwrap()).unwrap();
4597        let program_id = json
4598            .as_object()
4599            .unwrap()
4600            .get("programId")
4601            .unwrap()
4602            .as_str()
4603            .unwrap();
4604
4605        assert_eq!(
4606            program_id.parse::<Pubkey>().unwrap(),
4607            program_pubkey.pubkey()
4608        );
4609    }
4610
4611    /// Minimal ELF64 header buffer with a valid `e_ident` and `e_flags`
4612    /// (which encodes the SBPF version).
4613    fn fake_elf(e_flags: u32) -> Vec<u8> {
4614        let mut bytes = vec![0u8; 64];
4615        bytes[..4].copy_from_slice(&ELFMAG);
4616        bytes[4] = ELFCLASS64;
4617        bytes[5] = ELFDATA2LSB;
4618        bytes[6] = EV_CURRENT as u8;
4619        bytes[EI_OSABI as usize] = ELFOSABI_NONE;
4620        bytes[48..52].copy_from_slice(&e_flags.to_le_bytes());
4621        bytes
4622    }
4623
4624    fn deploy_config(versions: std::ops::RangeInclusive<SBPFVersion>) -> Config {
4625        Config {
4626            enabled_sbpf_versions: versions,
4627            ..Config::default()
4628        }
4629    }
4630
4631    #[test]
4632    fn test_explain_unsupported_sbpf_version() {
4633        let elf = fake_elf(1); // e_flags=1 -> SBPF v1
4634        let config = deploy_config(SBPFVersion::V3..=SBPFVersion::V3);
4635        let err = EbpfError::ElfError(ElfError::UnsupportedSBPFVersion);
4636        let msg = explain_elf_error(&err, &elf, &config);
4637        assert!(msg.contains("SBPF v1"), "got: {msg}");
4638        assert!(msg.contains("enabled: v3"), "got: {msg}");
4639    }
4640}