anchor_cli/
lib.rs

1use crate::config::{
2    get_default_ledger_path, BootstrapMode, BuildConfig, Config, ConfigOverride, Manifest,
3    PackageManager, ProgramArch, ProgramDeployment, ProgramWorkspace, ScriptsConfig, TestValidator,
4    WithPath, SHUTDOWN_WAIT, STARTUP_WAIT,
5};
6use anchor_client::Cluster;
7use anchor_lang::idl::{IdlAccount, IdlInstruction, ERASED_AUTHORITY};
8use anchor_lang::prelude::UpgradeableLoaderState;
9use anchor_lang::solana_program::bpf_loader_upgradeable;
10use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize, Discriminator};
11use anchor_lang_idl::convert::convert_idl;
12use anchor_lang_idl::types::{Idl, IdlArrayLen, IdlDefinedFields, IdlType, IdlTypeDefTy};
13use anyhow::{anyhow, bail, Context, Result};
14use checks::{check_anchor_version, check_deps, check_idl_build_feature, check_overflow};
15use clap::{CommandFactory, Parser};
16use dirs::home_dir;
17use flate2::read::ZlibDecoder;
18use flate2::write::ZlibEncoder;
19use flate2::Compression;
20use heck::{ToKebabCase, ToLowerCamelCase, ToPascalCase, ToSnakeCase};
21use regex::{Regex, RegexBuilder};
22use rust_template::{ProgramTemplate, TestTemplate};
23use semver::{Version, VersionReq};
24use serde_json::{json, Map, Value as JsonValue};
25use solana_rpc_client::rpc_client::RpcClient;
26use solana_sdk::commitment_config::CommitmentConfig;
27use solana_sdk::compute_budget::ComputeBudgetInstruction;
28use solana_sdk::instruction::{AccountMeta, Instruction};
29use solana_sdk::pubkey::Pubkey;
30use solana_sdk::signature::Keypair;
31use solana_sdk::signature::Signer;
32use solana_sdk::signer::EncodableKey;
33use solana_sdk::transaction::Transaction;
34use std::collections::BTreeMap;
35use std::collections::HashMap;
36use std::collections::HashSet;
37use std::ffi::OsString;
38use std::fs::{self, File};
39use std::io::prelude::*;
40use std::path::{Path, PathBuf};
41use std::process::{Child, Stdio};
42use std::str::FromStr;
43use std::string::ToString;
44use std::sync::LazyLock;
45
46mod checks;
47pub mod config;
48pub mod rust_template;
49
50// Version of the docker image.
51pub const VERSION: &str = env!("CARGO_PKG_VERSION");
52pub const DOCKER_BUILDER_VERSION: &str = VERSION;
53
54/// Default RPC port
55pub const DEFAULT_RPC_PORT: u16 = 8899;
56
57pub static AVM_HOME: LazyLock<PathBuf> = LazyLock::new(|| {
58    if let Ok(avm_home) = std::env::var("AVM_HOME") {
59        PathBuf::from(avm_home)
60    } else {
61        let mut user_home = dirs::home_dir().expect("Could not find home directory");
62        user_home.push(".avm");
63        user_home
64    }
65});
66
67#[derive(Debug, Parser)]
68#[clap(version = VERSION)]
69pub struct Opts {
70    #[clap(flatten)]
71    pub cfg_override: ConfigOverride,
72    #[clap(subcommand)]
73    pub command: Command,
74}
75
76#[derive(Debug, Parser)]
77pub enum Command {
78    /// Initializes a workspace.
79    Init {
80        /// Workspace name
81        name: String,
82        /// Use JavaScript instead of TypeScript
83        #[clap(short, long)]
84        javascript: bool,
85        /// Don't install JavaScript dependencies
86        #[clap(long)]
87        no_install: bool,
88        /// Package Manager to use
89        #[clap(value_enum, long, default_value = "yarn")]
90        package_manager: PackageManager,
91        /// Don't initialize git
92        #[clap(long)]
93        no_git: bool,
94        /// Rust program template to use
95        #[clap(value_enum, short, long, default_value = "single")]
96        template: ProgramTemplate,
97        /// Test template to use
98        #[clap(value_enum, long, default_value = "mocha")]
99        test_template: TestTemplate,
100        /// Initialize even if there are files
101        #[clap(long, action)]
102        force: bool,
103    },
104    /// Builds the workspace.
105    #[clap(name = "build", alias = "b")]
106    Build {
107        /// True if the build should not fail even if there are no "CHECK" comments
108        #[clap(long)]
109        skip_lint: bool,
110        /// Do not build the IDL
111        #[clap(long)]
112        no_idl: bool,
113        /// Output directory for the IDL.
114        #[clap(short, long)]
115        idl: Option<String>,
116        /// Output directory for the TypeScript IDL.
117        #[clap(short = 't', long)]
118        idl_ts: Option<String>,
119        /// True if the build artifact needs to be deterministic and verifiable.
120        #[clap(short, long)]
121        verifiable: bool,
122        /// Name of the program to build
123        #[clap(short, long)]
124        program_name: Option<String>,
125        /// Version of the Solana toolchain to use. For --verifiable builds
126        /// only.
127        #[clap(short, long)]
128        solana_version: Option<String>,
129        /// Docker image to use. For --verifiable builds only.
130        #[clap(short, long)]
131        docker_image: Option<String>,
132        /// Bootstrap docker image from scratch, installing all requirements for
133        /// verifiable builds. Only works for debian-based images.
134        #[clap(value_enum, short, long, default_value = "none")]
135        bootstrap: BootstrapMode,
136        /// Environment variables to pass into the docker container
137        #[clap(short, long, required = false)]
138        env: Vec<String>,
139        /// Arguments to pass to the underlying `cargo build-sbf` command
140        #[clap(required = false, last = true)]
141        cargo_args: Vec<String>,
142        /// Suppress doc strings in IDL output
143        #[clap(long)]
144        no_docs: bool,
145        /// Architecture to use when building the program
146        #[clap(value_enum, long, default_value = "sbf")]
147        arch: ProgramArch,
148    },
149    /// Expands macros (wrapper around cargo expand)
150    ///
151    /// Use it in a program folder to expand program
152    ///
153    /// Use it in a workspace but outside a program
154    /// folder to expand the entire workspace
155    Expand {
156        /// Expand only this program
157        #[clap(short, long)]
158        program_name: Option<String>,
159        /// Arguments to pass to the underlying `cargo expand` command
160        #[clap(required = false, last = true)]
161        cargo_args: Vec<String>,
162    },
163    /// Verifies the on-chain bytecode matches the locally compiled artifact.
164    /// Run this command inside a program subdirectory, i.e., in the dir
165    /// containing the program's Cargo.toml.
166    Verify {
167        /// The program ID to verify.
168        program_id: Pubkey,
169        /// The URL of the repository to verify against. Conflicts with `--current-dir`.
170        #[clap(long, conflicts_with = "current_dir")]
171        repo_url: Option<String>,
172        /// The commit hash to verify against. Requires `--repo-url`.
173        #[clap(long, requires = "repo_url")]
174        commit_hash: Option<String>,
175        /// Verify against the source code in the current directory. Conflicts with `--repo-url`.
176        #[clap(long)]
177        current_dir: bool,
178        /// Name of the program to run the command on. Defaults to the package name.
179        #[clap(long)]
180        program_name: Option<String>,
181        /// Any additional arguments to pass to `solana-verify`.
182        #[clap(raw = true)]
183        args: Vec<String>,
184    },
185    #[clap(name = "test", alias = "t")]
186    /// Runs integration tests.
187    Test {
188        /// Build and test only this program
189        #[clap(short, long)]
190        program_name: Option<String>,
191        /// Use this flag if you want to run tests against previously deployed
192        /// programs.
193        #[clap(long)]
194        skip_deploy: bool,
195        /// True if the build should not fail even if there are
196        /// no "CHECK" comments where normally required
197        #[clap(long)]
198        skip_lint: bool,
199        /// Flag to skip starting a local validator, if the configured cluster
200        /// url is a localnet.
201        #[clap(long)]
202        skip_local_validator: bool,
203        /// Flag to skip building the program in the workspace,
204        /// use this to save time when running test and the program code is not altered.
205        #[clap(long)]
206        skip_build: bool,
207        /// Do not build the IDL
208        #[clap(long)]
209        no_idl: bool,
210        /// Architecture to use when building the program
211        #[clap(value_enum, long, default_value = "sbf")]
212        arch: ProgramArch,
213        /// Flag to keep the local validator running after tests
214        /// to be able to check the transactions.
215        #[clap(long)]
216        detach: bool,
217        /// Run the test suites under the specified path
218        #[clap(long)]
219        run: Vec<String>,
220        args: Vec<String>,
221        /// Environment variables to pass into the docker container
222        #[clap(short, long, required = false)]
223        env: Vec<String>,
224        /// Arguments to pass to the underlying `cargo build-sbf` command.
225        #[clap(required = false, last = true)]
226        cargo_args: Vec<String>,
227    },
228    /// Creates a new program.
229    New {
230        /// Program name
231        name: String,
232        /// Rust program template to use
233        #[clap(value_enum, short, long, default_value = "single")]
234        template: ProgramTemplate,
235        /// Create new program even if there is already one
236        #[clap(long, action)]
237        force: bool,
238    },
239    /// Commands for interacting with interface definitions.
240    Idl {
241        #[clap(subcommand)]
242        subcmd: IdlCommand,
243    },
244    /// Remove all artifacts from the generated directories except program keypairs.
245    Clean,
246    /// Deploys each program in the workspace.
247    Deploy {
248        /// Only deploy this program
249        #[clap(short, long)]
250        program_name: Option<String>,
251        /// Keypair of the program (filepath) (requires program-name)
252        #[clap(long, requires = "program_name")]
253        program_keypair: Option<String>,
254        /// If true, deploy from path target/verifiable
255        #[clap(short, long)]
256        verifiable: bool,
257        /// Don't upload IDL during deployment (IDL is uploaded by default)
258        #[clap(long)]
259        no_idl: bool,
260        /// Arguments to pass to the underlying `solana program deploy` command.
261        #[clap(required = false, last = true)]
262        solana_args: Vec<String>,
263    },
264    /// Runs the deploy migration script.
265    Migrate,
266    /// Deploys, initializes an IDL, and migrates all in one command.
267    /// Upgrades a single program. The configured wallet must be the upgrade
268    /// authority.
269    Upgrade {
270        /// The program to upgrade.
271        #[clap(short, long)]
272        program_id: Pubkey,
273        /// Filepath to the new program binary.
274        program_filepath: String,
275        /// Max times to retry on failure.
276        #[clap(long, default_value = "0")]
277        max_retries: u32,
278        /// Arguments to pass to the underlying `solana program deploy` command.
279        #[clap(required = false, last = true)]
280        solana_args: Vec<String>,
281    },
282    #[cfg(feature = "dev")]
283    /// Runs an airdrop loop, continuously funding the configured wallet.
284    Airdrop {
285        #[clap(short, long)]
286        url: Option<String>,
287    },
288    /// Cluster commands.
289    Cluster {
290        #[clap(subcommand)]
291        subcmd: ClusterCommand,
292    },
293    /// Starts a node shell with an Anchor client setup according to the local
294    /// config.
295    Shell,
296    /// Runs the script defined by the current workspace's Anchor.toml.
297    Run {
298        /// The name of the script to run.
299        script: String,
300        /// Argument to pass to the underlying script.
301        #[clap(required = false, last = true)]
302        script_args: Vec<String>,
303    },
304    /// Saves an api token from the registry locally.
305    Login {
306        /// API access token.
307        token: String,
308    },
309    /// Program keypair commands.
310    Keys {
311        #[clap(subcommand)]
312        subcmd: KeysCommand,
313    },
314    /// Localnet commands.
315    Localnet {
316        /// Flag to skip building the program in the workspace,
317        /// use this to save time when running test and the program code is not altered.
318        #[clap(long)]
319        skip_build: bool,
320        /// Use this flag if you want to run tests against previously deployed
321        /// programs.
322        #[clap(long)]
323        skip_deploy: bool,
324        /// True if the build should not fail even if there are
325        /// no "CHECK" comments where normally required
326        #[clap(long)]
327        skip_lint: bool,
328        /// Architecture to use when building the program
329        #[clap(value_enum, long, default_value = "sbf")]
330        arch: ProgramArch,
331        /// Environment variables to pass into the docker container
332        #[clap(short, long, required = false)]
333        env: Vec<String>,
334        /// Arguments to pass to the underlying `cargo build-sbf` command.
335        #[clap(required = false, last = true)]
336        cargo_args: Vec<String>,
337    },
338    /// Fetch and deserialize an account using the IDL provided.
339    Account {
340        /// Account struct to deserialize
341        account_type: String,
342        /// Address of the account to deserialize
343        address: Pubkey,
344        /// IDL to use (defaults to workspace IDL)
345        #[clap(long)]
346        idl: Option<String>,
347    },
348    /// Generates shell completions.
349    Completions {
350        #[clap(value_enum)]
351        shell: clap_complete::Shell,
352    },
353}
354
355#[derive(Debug, Parser)]
356pub enum KeysCommand {
357    /// List all of the program keys.
358    List,
359    /// Sync program `declare_id!` pubkeys with the program's actual pubkey.
360    Sync {
361        /// Only sync the given program instead of all programs
362        #[clap(short, long)]
363        program_name: Option<String>,
364    },
365}
366
367#[derive(Debug, Parser)]
368pub enum IdlCommand {
369    /// Initializes a program's IDL account. Can only be run once.
370    Init {
371        program_id: Pubkey,
372        #[clap(short, long)]
373        filepath: String,
374        #[clap(long)]
375        priority_fee: Option<u64>,
376    },
377    Close {
378        program_id: Pubkey,
379        /// The IDL account to close. If none is given, then the IDL account derived from program_id is used.
380        #[clap(long)]
381        idl_address: Option<Pubkey>,
382        /// When used, the content of the instruction will only be printed in base64 form and not executed.
383        /// Useful for multisig execution when the local wallet keypair is not available.
384        #[clap(long)]
385        print_only: bool,
386        #[clap(long)]
387        priority_fee: Option<u64>,
388    },
389    /// Writes an IDL into a buffer account. This can be used with SetBuffer
390    /// to perform an upgrade.
391    WriteBuffer {
392        program_id: Pubkey,
393        #[clap(short, long)]
394        filepath: String,
395        #[clap(long)]
396        priority_fee: Option<u64>,
397    },
398    /// Sets a new IDL buffer for the program.
399    SetBuffer {
400        program_id: Pubkey,
401        /// Address of the buffer account to set as the idl on the program.
402        #[clap(short, long)]
403        buffer: Pubkey,
404        /// When used, the content of the instruction will only be printed in base64 form and not executed.
405        /// Useful for multisig execution when the local wallet keypair is not available.
406        #[clap(long)]
407        print_only: bool,
408        #[clap(long)]
409        priority_fee: Option<u64>,
410    },
411    /// Upgrades the IDL to the new file. An alias for first writing and then
412    /// then setting the idl buffer account.
413    Upgrade {
414        program_id: Pubkey,
415        #[clap(short, long)]
416        filepath: String,
417        #[clap(long)]
418        priority_fee: Option<u64>,
419    },
420    /// Sets a new authority on the IDL account.
421    SetAuthority {
422        /// The IDL account buffer to set the authority of. If none is given,
423        /// then the canonical IDL account is used.
424        address: Option<Pubkey>,
425        /// Program to change the IDL authority.
426        #[clap(short, long)]
427        program_id: Pubkey,
428        /// New authority of the IDL account.
429        #[clap(short, long)]
430        new_authority: Pubkey,
431        /// When used, the content of the instruction will only be printed in base64 form and not executed.
432        /// Useful for multisig execution when the local wallet keypair is not available.
433        #[clap(long)]
434        print_only: bool,
435        #[clap(long)]
436        priority_fee: Option<u64>,
437    },
438    /// Command to remove the ability to modify the IDL account. This should
439    /// likely be used in conjection with eliminating an "upgrade authority" on
440    /// the program.
441    EraseAuthority {
442        #[clap(short, long)]
443        program_id: Pubkey,
444        #[clap(long)]
445        priority_fee: Option<u64>,
446    },
447    /// Outputs the authority for the IDL account.
448    Authority {
449        /// The program to view.
450        program_id: Pubkey,
451    },
452    /// Generates the IDL for the program using the compilation method.
453    #[clap(alias = "b")]
454    Build {
455        // Program name to build the IDL of(current dir's program if not specified)
456        #[clap(short, long)]
457        program_name: Option<String>,
458        /// Output file for the IDL (stdout if not specified)
459        #[clap(short, long)]
460        out: Option<String>,
461        /// Output file for the TypeScript IDL
462        #[clap(short = 't', long)]
463        out_ts: Option<String>,
464        /// Suppress doc strings in output
465        #[clap(long)]
466        no_docs: bool,
467        /// Do not check for safety comments
468        #[clap(long)]
469        skip_lint: bool,
470        /// Arguments to pass to the underlying `cargo test` command
471        #[clap(required = false, last = true)]
472        cargo_args: Vec<String>,
473    },
474    /// Fetches an IDL for the given address from a cluster.
475    /// The address can be a program, IDL account, or IDL buffer.
476    Fetch {
477        address: Pubkey,
478        /// Output file for the IDL (stdout if not specified).
479        #[clap(short, long)]
480        out: Option<String>,
481    },
482    /// Convert legacy IDLs (pre Anchor 0.30) to the new IDL spec
483    Convert {
484        /// Path to the IDL file
485        path: String,
486        /// Output file for the IDL (stdout if not specified)
487        #[clap(short, long)]
488        out: Option<String>,
489        /// Address to use (defaults to `metadata.address` value)
490        #[clap(short, long)]
491        program_id: Option<Pubkey>,
492    },
493    /// Generate TypeScript type for the IDL
494    Type {
495        /// Path to the IDL file
496        path: String,
497        /// Output file for the IDL (stdout if not specified)
498        #[clap(short, long)]
499        out: Option<String>,
500    },
501}
502
503#[derive(Debug, Parser)]
504pub enum ClusterCommand {
505    /// Prints common cluster urls.
506    List,
507}
508
509fn get_keypair(path: &str) -> Result<Keypair> {
510    solana_sdk::signature::read_keypair_file(path)
511        .map_err(|_| anyhow!("Unable to read keypair file ({path})"))
512}
513
514pub fn entry(opts: Opts) -> Result<()> {
515    let restore_cbs = override_toolchain(&opts.cfg_override)?;
516    let result = process_command(opts);
517    restore_toolchain(restore_cbs)?;
518
519    result
520}
521
522/// Functions to restore toolchain entries
523type RestoreToolchainCallbacks = Vec<Box<dyn FnOnce() -> Result<()>>>;
524
525/// Override the toolchain from `Anchor.toml`.
526///
527/// Returns the previous versions to restore back to.
528fn override_toolchain(cfg_override: &ConfigOverride) -> Result<RestoreToolchainCallbacks> {
529    let mut restore_cbs: RestoreToolchainCallbacks = vec![];
530
531    let cfg = Config::discover(cfg_override)?;
532    if let Some(cfg) = cfg {
533        fn parse_version(text: &str) -> Option<String> {
534            Some(
535                Regex::new(r"(\d+\.\d+\.\S+)")
536                    .unwrap()
537                    .captures_iter(text)
538                    .next()?
539                    .get(0)?
540                    .as_str()
541                    .to_string(),
542            )
543        }
544
545        fn get_current_version(cmd_name: &str) -> Result<String> {
546            let output = std::process::Command::new(cmd_name)
547                .arg("--version")
548                .output()?;
549            if !output.status.success() {
550                return Err(anyhow!("Failed to run `{cmd_name} --version`"));
551            }
552
553            let output_version = std::str::from_utf8(&output.stdout)?;
554            parse_version(output_version)
555                .ok_or_else(|| anyhow!("Failed to parse the version of `{cmd_name}`"))
556        }
557
558        if let Some(solana_version) = &cfg.toolchain.solana_version {
559            let current_version = get_current_version("solana")?;
560            if solana_version != &current_version {
561                // We are overriding with `solana-install` command instead of using the binaries
562                // from `~/.local/share/solana/install/releases` because we use multiple Solana
563                // binaries in various commands.
564                fn override_solana_version(version: String) -> Result<bool> {
565                    // There is a deprecation warning message starting with `1.18.19` which causes
566                    // parsing problems https://github.com/coral-xyz/anchor/issues/3147
567                    let (cmd_name, domain) =
568                        if Version::parse(&version)? < Version::parse("1.18.19")? {
569                            ("solana-install", "solana.com")
570                        } else {
571                            ("agave-install", "anza.xyz")
572                        };
573
574                    // Install the command if it's not installed
575                    if get_current_version(cmd_name).is_err() {
576                        // `solana-install` and `agave-install` are not usable at the same time i.e.
577                        // using one of them makes the other unusable with the default installation,
578                        // causing the installation process to run each time users switch between
579                        // `agave` supported versions. For example, if the user's active Solana
580                        // version is `1.18.17`, and he specifies `solana_version = "2.0.6"`, this
581                        // code path will run each time an Anchor command gets executed.
582                        eprintln!(
583                            "Command not installed: `{cmd_name}`. \
584                            See https://github.com/anza-xyz/agave/wiki/Agave-Transition, \
585                            installing..."
586                        );
587                        let install_script = std::process::Command::new("curl")
588                            .args([
589                                "-sSfL",
590                                &format!("https://release.{domain}/v{version}/install"),
591                            ])
592                            .output()?;
593                        let is_successful = std::process::Command::new("sh")
594                            .args(["-c", std::str::from_utf8(&install_script.stdout)?])
595                            .spawn()?
596                            .wait_with_output()?
597                            .status
598                            .success();
599                        if !is_successful {
600                            return Err(anyhow!("Failed to install `{cmd_name}`"));
601                        }
602                    }
603
604                    let output = std::process::Command::new(cmd_name).arg("list").output()?;
605                    if !output.status.success() {
606                        return Err(anyhow!("Failed to list installed `solana` versions"));
607                    }
608
609                    // Hide the installation progress if the version is already installed
610                    let is_installed = std::str::from_utf8(&output.stdout)?
611                        .lines()
612                        .filter_map(parse_version)
613                        .any(|line_version| line_version == version);
614                    let (stderr, stdout) = if is_installed {
615                        (Stdio::null(), Stdio::null())
616                    } else {
617                        (Stdio::inherit(), Stdio::inherit())
618                    };
619
620                    std::process::Command::new(cmd_name)
621                        .arg("init")
622                        .arg(&version)
623                        .stderr(stderr)
624                        .stdout(stdout)
625                        .spawn()?
626                        .wait()
627                        .map(|status| status.success())
628                        .map_err(|err| anyhow!("Failed to run `{cmd_name}` command: {err}"))
629                }
630
631                match override_solana_version(solana_version.to_owned())? {
632                    true => restore_cbs.push(Box::new(|| {
633                        match override_solana_version(current_version)? {
634                            true => Ok(()),
635                            false => Err(anyhow!("Failed to restore `solana` version")),
636                        }
637                    })),
638                    false => eprintln!(
639                        "Failed to override `solana` version to {solana_version}, \
640                        using {current_version} instead"
641                    ),
642                }
643            }
644        }
645
646        // Anchor version override should be handled last
647        if let Some(anchor_version) = &cfg.toolchain.anchor_version {
648            // Anchor binary name prefix(applies to binaries that are installed via `avm`)
649            const ANCHOR_BINARY_PREFIX: &str = "anchor-";
650
651            // Get the current version from the executing binary name if possible because commit
652            // based toolchain overrides do not have version information.
653            let current_version = std::env::args()
654                .next()
655                .expect("First arg should exist")
656                .parse::<PathBuf>()?
657                .file_name()
658                .and_then(|name| name.to_str())
659                .expect("File name should be valid Unicode")
660                .split_once(ANCHOR_BINARY_PREFIX)
661                .map(|(_, version)| version)
662                .unwrap_or(VERSION)
663                .to_owned();
664            if anchor_version != &current_version {
665                let binary_path = home_dir()
666                    .unwrap()
667                    .join(".avm")
668                    .join("bin")
669                    .join(format!("{ANCHOR_BINARY_PREFIX}{anchor_version}"));
670
671                if !binary_path.exists() {
672                    eprintln!(
673                        "`anchor` {anchor_version} is not installed with `avm`. Installing...\n"
674                    );
675
676                    if let Err(e) = install_with_avm(anchor_version, false) {
677                        eprintln!(
678                            "Failed to install `anchor`: {e}, using {current_version} instead"
679                        );
680                        return Ok(restore_cbs);
681                    }
682                }
683
684                let exit_code = std::process::Command::new(binary_path)
685                    .args(std::env::args_os().skip(1))
686                    .spawn()?
687                    .wait()?
688                    .code()
689                    .unwrap_or(1);
690                restore_toolchain(restore_cbs)?;
691                std::process::exit(exit_code);
692            }
693        }
694    }
695
696    Ok(restore_cbs)
697}
698
699/// Installs Anchor using AVM, passing `--force` (and optionally) installing
700/// `solana-verify`.
701fn install_with_avm(version: &str, verify: bool) -> Result<()> {
702    let mut cmd = std::process::Command::new("avm");
703    cmd.arg("install");
704    cmd.arg(version);
705    cmd.arg("--force");
706    if verify {
707        cmd.arg("--verify");
708    }
709    let status = cmd.status().context("running AVM")?;
710    if !status.success() {
711        bail!("failed to install `anchor` {version} with avm");
712    }
713    Ok(())
714}
715
716/// Restore toolchain to how it was before the command was run.
717fn restore_toolchain(restore_cbs: RestoreToolchainCallbacks) -> Result<()> {
718    for restore_toolchain in restore_cbs {
719        if let Err(e) = restore_toolchain() {
720            eprintln!("Toolchain error: {e}");
721        }
722    }
723
724    Ok(())
725}
726
727/// Get the system's default license - what 'npm init' would use.
728fn get_npm_init_license() -> Result<String> {
729    let npm_init_license_output = std::process::Command::new("npm")
730        .arg("config")
731        .arg("get")
732        .arg("init-license")
733        .output()?;
734
735    if !npm_init_license_output.status.success() {
736        return Err(anyhow!("Failed to get npm init license"));
737    }
738
739    let license = String::from_utf8(npm_init_license_output.stdout)?;
740    Ok(license.trim().to_string())
741}
742
743fn process_command(opts: Opts) -> Result<()> {
744    match opts.command {
745        Command::Init {
746            name,
747            javascript,
748            no_install,
749            package_manager,
750            no_git,
751            template,
752            test_template,
753            force,
754        } => init(
755            &opts.cfg_override,
756            name,
757            javascript,
758            no_install,
759            package_manager,
760            no_git,
761            template,
762            test_template,
763            force,
764        ),
765        Command::New {
766            name,
767            template,
768            force,
769        } => new(&opts.cfg_override, name, template, force),
770        Command::Build {
771            no_idl,
772            idl,
773            idl_ts,
774            verifiable,
775            program_name,
776            solana_version,
777            docker_image,
778            bootstrap,
779            cargo_args,
780            env,
781            skip_lint,
782            no_docs,
783            arch,
784        } => build(
785            &opts.cfg_override,
786            no_idl,
787            idl,
788            idl_ts,
789            verifiable,
790            skip_lint,
791            program_name,
792            solana_version,
793            docker_image,
794            bootstrap,
795            None,
796            None,
797            env,
798            cargo_args,
799            no_docs,
800            arch,
801        ),
802        Command::Verify {
803            program_id,
804            repo_url,
805            commit_hash,
806            current_dir,
807            program_name,
808            args,
809        } => verify(
810            program_id,
811            repo_url,
812            commit_hash,
813            current_dir,
814            program_name,
815            args,
816        ),
817        Command::Clean => clean(&opts.cfg_override),
818        Command::Deploy {
819            program_name,
820            program_keypair,
821            verifiable,
822            no_idl,
823            solana_args,
824        } => deploy(
825            &opts.cfg_override,
826            program_name,
827            program_keypair,
828            verifiable,
829            no_idl,
830            solana_args,
831        ),
832        Command::Expand {
833            program_name,
834            cargo_args,
835        } => expand(&opts.cfg_override, program_name, &cargo_args),
836        Command::Upgrade {
837            program_id,
838            program_filepath,
839            max_retries,
840            solana_args,
841        } => upgrade(
842            &opts.cfg_override,
843            program_id,
844            program_filepath,
845            max_retries,
846            solana_args,
847        ),
848        Command::Idl { subcmd } => idl(&opts.cfg_override, subcmd),
849        Command::Migrate => migrate(&opts.cfg_override),
850        Command::Test {
851            program_name,
852            skip_deploy,
853            skip_local_validator,
854            skip_build,
855            no_idl,
856            detach,
857            run,
858            args,
859            env,
860            cargo_args,
861            skip_lint,
862            arch,
863        } => test(
864            &opts.cfg_override,
865            program_name,
866            skip_deploy,
867            skip_local_validator,
868            skip_build,
869            skip_lint,
870            no_idl,
871            detach,
872            run,
873            args,
874            env,
875            cargo_args,
876            arch,
877        ),
878        #[cfg(feature = "dev")]
879        Command::Airdrop { .. } => airdrop(&opts.cfg_override),
880        Command::Cluster { subcmd } => cluster(subcmd),
881        Command::Shell => shell(&opts.cfg_override),
882        Command::Run {
883            script,
884            script_args,
885        } => run(&opts.cfg_override, script, script_args),
886        Command::Login { token } => login(&opts.cfg_override, token),
887        Command::Keys { subcmd } => keys(&opts.cfg_override, subcmd),
888        Command::Localnet {
889            skip_build,
890            skip_deploy,
891            skip_lint,
892            env,
893            cargo_args,
894            arch,
895        } => localnet(
896            &opts.cfg_override,
897            skip_build,
898            skip_deploy,
899            skip_lint,
900            env,
901            cargo_args,
902            arch,
903        ),
904        Command::Account {
905            account_type,
906            address,
907            idl,
908        } => account(&opts.cfg_override, account_type, address, idl),
909        Command::Completions { shell } => {
910            clap_complete::generate(
911                shell,
912                &mut Opts::command(),
913                "anchor",
914                &mut std::io::stdout(),
915            );
916            Ok(())
917        }
918    }
919}
920
921#[allow(clippy::too_many_arguments)]
922fn init(
923    cfg_override: &ConfigOverride,
924    name: String,
925    javascript: bool,
926    no_install: bool,
927    package_manager: PackageManager,
928    no_git: bool,
929    template: ProgramTemplate,
930    test_template: TestTemplate,
931    force: bool,
932) -> Result<()> {
933    if !force && Config::discover(cfg_override)?.is_some() {
934        return Err(anyhow!("Workspace already initialized"));
935    }
936
937    // We need to format different cases for the dir and the name
938    let rust_name = name.to_snake_case();
939    let project_name = if name == rust_name {
940        rust_name.clone()
941    } else {
942        name.to_kebab_case()
943    };
944
945    // Additional keywords that have not been added to the `syn` crate as reserved words
946    // https://github.com/dtolnay/syn/pull/1098
947    let extra_keywords = ["async", "await", "try"];
948    // Anchor converts to snake case before writing the program name
949    if syn::parse_str::<syn::Ident>(&rust_name).is_err()
950        || extra_keywords.contains(&rust_name.as_str())
951    {
952        return Err(anyhow!(
953            "Anchor workspace name must be a valid Rust identifier. It may not be a Rust reserved word, start with a digit, or include certain disallowed characters. See https://doc.rust-lang.org/reference/identifiers.html for more detail.",
954        ));
955    }
956
957    if force {
958        fs::create_dir_all(&project_name)?;
959    } else {
960        fs::create_dir(&project_name)?;
961    }
962    std::env::set_current_dir(&project_name)?;
963    fs::create_dir_all("app")?;
964
965    let mut cfg = Config::default();
966
967    let test_script = test_template.get_test_script(javascript, &package_manager);
968    cfg.scripts.insert("test".to_owned(), test_script);
969
970    let package_manager_cmd = package_manager.to_string();
971    cfg.toolchain.package_manager = Some(package_manager);
972
973    let mut localnet = BTreeMap::new();
974    let program_id = rust_template::get_or_create_program_id(&rust_name);
975    localnet.insert(
976        rust_name,
977        ProgramDeployment {
978            address: program_id,
979            path: None,
980            idl: None,
981        },
982    );
983    cfg.programs.insert(Cluster::Localnet, localnet);
984    let toml = cfg.to_string();
985    fs::write("Anchor.toml", toml)?;
986
987    // Initialize .gitignore file
988    fs::write(".gitignore", rust_template::git_ignore())?;
989
990    // Initialize .prettierignore file
991    fs::write(".prettierignore", rust_template::prettier_ignore())?;
992
993    // Remove the default program if `--force` is passed
994    if force {
995        fs::remove_dir_all(
996            std::env::current_dir()?
997                .join("programs")
998                .join(&project_name),
999        )?;
1000    }
1001
1002    // Build the program.
1003    rust_template::create_program(
1004        &project_name,
1005        template,
1006        TestTemplate::Mollusk == test_template,
1007    )?;
1008
1009    // Build the migrations directory.
1010    let migrations_path = Path::new("migrations");
1011    fs::create_dir_all(migrations_path)?;
1012
1013    let license = get_npm_init_license()?;
1014
1015    let jest = TestTemplate::Jest == test_template;
1016    if javascript {
1017        // Build javascript config
1018        let mut package_json = File::create("package.json")?;
1019        package_json.write_all(rust_template::package_json(jest, license).as_bytes())?;
1020
1021        let mut deploy = File::create(migrations_path.join("deploy.js"))?;
1022        deploy.write_all(rust_template::deploy_script().as_bytes())?;
1023    } else {
1024        // Build typescript config
1025        let mut ts_config = File::create("tsconfig.json")?;
1026        ts_config.write_all(rust_template::ts_config(jest).as_bytes())?;
1027
1028        let mut ts_package_json = File::create("package.json")?;
1029        ts_package_json.write_all(rust_template::ts_package_json(jest, license).as_bytes())?;
1030
1031        let mut deploy = File::create(migrations_path.join("deploy.ts"))?;
1032        deploy.write_all(rust_template::ts_deploy_script().as_bytes())?;
1033    }
1034
1035    test_template.create_test_files(&project_name, javascript, &program_id.to_string())?;
1036
1037    if !no_install {
1038        let package_manager_result = install_node_modules(&package_manager_cmd)?;
1039
1040        if !package_manager_result.status.success() && package_manager_cmd != "npm" {
1041            println!("Failed {package_manager_cmd} install will attempt to npm install");
1042            install_node_modules("npm")?;
1043        } else {
1044            eprintln!("Failed to install node modules");
1045        }
1046    }
1047
1048    if !no_git {
1049        let git_result = std::process::Command::new("git")
1050            .arg("init")
1051            .stdout(Stdio::inherit())
1052            .stderr(Stdio::inherit())
1053            .output()
1054            .map_err(|e| anyhow::format_err!("git init failed: {}", e.to_string()))?;
1055        if !git_result.status.success() {
1056            eprintln!("Failed to automatically initialize a new git repository");
1057        }
1058    }
1059
1060    println!("{project_name} initialized");
1061
1062    Ok(())
1063}
1064
1065fn install_node_modules(cmd: &str) -> Result<std::process::Output> {
1066    if cfg!(target_os = "windows") {
1067        std::process::Command::new("cmd")
1068            .arg(format!("/C {cmd} install"))
1069            .stdout(Stdio::inherit())
1070            .stderr(Stdio::inherit())
1071            .output()
1072            .map_err(|e| anyhow::format_err!("{} install failed: {}", cmd, e.to_string()))
1073    } else {
1074        std::process::Command::new(cmd)
1075            .arg("install")
1076            .stdout(Stdio::inherit())
1077            .stderr(Stdio::inherit())
1078            .output()
1079            .map_err(|e| anyhow::format_err!("{} install failed: {}", cmd, e.to_string()))
1080    }
1081}
1082
1083// Creates a new program crate in the `programs/<name>` directory.
1084fn new(
1085    cfg_override: &ConfigOverride,
1086    name: String,
1087    template: ProgramTemplate,
1088    force: bool,
1089) -> Result<()> {
1090    with_workspace(cfg_override, |cfg| {
1091        match cfg.path().parent() {
1092            None => {
1093                println!("Unable to make new program");
1094            }
1095            Some(parent) => {
1096                std::env::set_current_dir(parent)?;
1097
1098                let cluster = cfg.provider.cluster.clone();
1099                let programs = cfg.programs.entry(cluster).or_default();
1100                if programs.contains_key(&name) {
1101                    if !force {
1102                        return Err(anyhow!("Program already exists"));
1103                    }
1104
1105                    // Delete all files within the program folder
1106                    fs::remove_dir_all(std::env::current_dir()?.join("programs").join(&name))?;
1107                }
1108
1109                rust_template::create_program(&name, template, false)?;
1110
1111                programs.insert(
1112                    name.clone(),
1113                    ProgramDeployment {
1114                        address: rust_template::get_or_create_program_id(&name),
1115                        path: None,
1116                        idl: None,
1117                    },
1118                );
1119
1120                let toml = cfg.to_string();
1121                fs::write("Anchor.toml", toml)?;
1122
1123                println!("Created new program.");
1124            }
1125        };
1126        Ok(())
1127    })
1128}
1129
1130/// Array of (path, content) tuple.
1131pub type Files = Vec<(PathBuf, String)>;
1132
1133/// Create files from the given (path, content) tuple array.
1134///
1135/// # Example
1136///
1137/// ```ignore
1138/// crate_files(vec![("programs/my_program/src/lib.rs".into(), "// Content".into())])?;
1139/// ```
1140pub fn create_files(files: &Files) -> Result<()> {
1141    for (path, content) in files {
1142        let path = path
1143            .display()
1144            .to_string()
1145            .replace('/', std::path::MAIN_SEPARATOR_STR);
1146        let path = Path::new(&path);
1147        if path.exists() {
1148            continue;
1149        }
1150
1151        match path.extension() {
1152            Some(_) => {
1153                fs::create_dir_all(path.parent().unwrap())?;
1154                fs::write(path, content)?;
1155            }
1156            None => fs::create_dir_all(path)?,
1157        }
1158    }
1159
1160    Ok(())
1161}
1162
1163/// Override or create files from the given (path, content) tuple array.
1164///
1165/// # Example
1166///
1167/// ```ignore
1168/// override_or_create_files(vec![("programs/my_program/src/lib.rs".into(), "// Content".into())])?;
1169/// ```
1170pub fn override_or_create_files(files: &Files) -> Result<()> {
1171    for (path, content) in files {
1172        let path = Path::new(path);
1173        if path.exists() {
1174            let mut f = fs::OpenOptions::new()
1175                .write(true)
1176                .truncate(true)
1177                .open(path)?;
1178            f.write_all(content.as_bytes())?;
1179            f.flush()?;
1180        } else {
1181            fs::create_dir_all(path.parent().unwrap())?;
1182            fs::write(path, content)?;
1183        }
1184    }
1185
1186    Ok(())
1187}
1188
1189pub fn expand(
1190    cfg_override: &ConfigOverride,
1191    program_name: Option<String>,
1192    cargo_args: &[String],
1193) -> Result<()> {
1194    // Change to the workspace member directory, if needed.
1195    if let Some(program_name) = program_name.as_ref() {
1196        cd_member(cfg_override, program_name)?;
1197    }
1198
1199    let workspace_cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
1200    let cfg_parent = workspace_cfg.path().parent().expect("Invalid Anchor.toml");
1201    let cargo = Manifest::discover()?;
1202
1203    let expansions_path = cfg_parent.join(".anchor").join("expanded-macros");
1204    fs::create_dir_all(&expansions_path)?;
1205
1206    match cargo {
1207        // No Cargo.toml found, expand entire workspace
1208        None => expand_all(&workspace_cfg, expansions_path, cargo_args),
1209        // Cargo.toml is at root of workspace, expand entire workspace
1210        Some(cargo) if cargo.path().parent() == workspace_cfg.path().parent() => {
1211            expand_all(&workspace_cfg, expansions_path, cargo_args)
1212        }
1213        // Reaching this arm means Cargo.toml belongs to a single package. Expand it.
1214        Some(cargo) => expand_program(
1215            // If we found Cargo.toml, it must be in a directory so unwrap is safe
1216            cargo.path().parent().unwrap().to_path_buf(),
1217            expansions_path,
1218            cargo_args,
1219        ),
1220    }
1221}
1222
1223fn expand_all(
1224    workspace_cfg: &WithPath<Config>,
1225    expansions_path: PathBuf,
1226    cargo_args: &[String],
1227) -> Result<()> {
1228    let cur_dir = std::env::current_dir()?;
1229    for p in workspace_cfg.get_rust_program_list()? {
1230        expand_program(p, expansions_path.clone(), cargo_args)?;
1231    }
1232    std::env::set_current_dir(cur_dir)?;
1233    Ok(())
1234}
1235
1236fn expand_program(
1237    program_path: PathBuf,
1238    expansions_path: PathBuf,
1239    cargo_args: &[String],
1240) -> Result<()> {
1241    let cargo = Manifest::from_path(program_path.join("Cargo.toml"))
1242        .map_err(|_| anyhow!("Could not find Cargo.toml for program"))?;
1243
1244    let target_dir_arg = {
1245        let mut target_dir_arg = OsString::from("--target-dir=");
1246        target_dir_arg.push(expansions_path.join("expand-target"));
1247        target_dir_arg
1248    };
1249
1250    let package_name = &cargo
1251        .package
1252        .as_ref()
1253        .ok_or_else(|| anyhow!("Cargo config is missing a package"))?
1254        .name;
1255    let program_expansions_path = expansions_path.join(package_name);
1256    fs::create_dir_all(&program_expansions_path)?;
1257
1258    let exit = std::process::Command::new("cargo")
1259        .arg("expand")
1260        .arg(target_dir_arg)
1261        .arg(format!("--package={package_name}"))
1262        .args(cargo_args)
1263        .stderr(Stdio::inherit())
1264        .output()
1265        .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
1266    if !exit.status.success() {
1267        eprintln!("'anchor expand' failed. Perhaps you have not installed 'cargo-expand'? https://github.com/dtolnay/cargo-expand#installation");
1268        std::process::exit(exit.status.code().unwrap_or(1));
1269    }
1270
1271    let version = cargo.version();
1272    let time = chrono::Utc::now().to_string().replace(' ', "_");
1273    let file_path = program_expansions_path.join(format!("{package_name}-{version}-{time}.rs"));
1274    fs::write(&file_path, &exit.stdout).map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
1275
1276    println!(
1277        "Expanded {} into file {}\n",
1278        package_name,
1279        file_path.to_string_lossy()
1280    );
1281    Ok(())
1282}
1283
1284#[allow(clippy::too_many_arguments)]
1285pub fn build(
1286    cfg_override: &ConfigOverride,
1287    no_idl: bool,
1288    idl: Option<String>,
1289    idl_ts: Option<String>,
1290    verifiable: bool,
1291    skip_lint: bool,
1292    program_name: Option<String>,
1293    solana_version: Option<String>,
1294    docker_image: Option<String>,
1295    bootstrap: BootstrapMode,
1296    stdout: Option<File>, // Used for the package registry server.
1297    stderr: Option<File>, // Used for the package registry server.
1298    env_vars: Vec<String>,
1299    cargo_args: Vec<String>,
1300    no_docs: bool,
1301    arch: ProgramArch,
1302) -> Result<()> {
1303    // Change to the workspace member directory, if needed.
1304    if let Some(program_name) = program_name.as_ref() {
1305        cd_member(cfg_override, program_name)?;
1306    }
1307    let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
1308    let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml");
1309
1310    // Require overflow checks
1311    let workspace_cargo_toml_path = cfg_parent.join("Cargo.toml");
1312    if workspace_cargo_toml_path.exists() {
1313        check_overflow(workspace_cargo_toml_path)?;
1314    }
1315
1316    // Check whether there is a mismatch between CLI and crate/package versions
1317    check_anchor_version(&cfg).ok();
1318    check_deps(&cfg).ok();
1319
1320    let idl_out = match idl {
1321        Some(idl) => Some(PathBuf::from(idl)),
1322        None => Some(cfg_parent.join("target").join("idl")),
1323    };
1324    fs::create_dir_all(idl_out.as_ref().unwrap())?;
1325
1326    let idl_ts_out = match idl_ts {
1327        Some(idl_ts) => Some(PathBuf::from(idl_ts)),
1328        None => Some(cfg_parent.join("target").join("types")),
1329    };
1330    fs::create_dir_all(idl_ts_out.as_ref().unwrap())?;
1331
1332    if !cfg.workspace.types.is_empty() {
1333        fs::create_dir_all(cfg_parent.join(&cfg.workspace.types))?;
1334    };
1335
1336    let cargo = Manifest::discover()?;
1337    let build_config = BuildConfig {
1338        verifiable,
1339        solana_version: solana_version.or_else(|| cfg.toolchain.solana_version.clone()),
1340        docker_image: docker_image.unwrap_or_else(|| cfg.docker()),
1341        bootstrap,
1342    };
1343    match cargo {
1344        // No Cargo.toml so build the entire workspace.
1345        None => build_all(
1346            &cfg,
1347            cfg.path(),
1348            no_idl,
1349            idl_out,
1350            idl_ts_out,
1351            &build_config,
1352            stdout,
1353            stderr,
1354            env_vars,
1355            cargo_args,
1356            skip_lint,
1357            no_docs,
1358            arch,
1359        )?,
1360        // If the Cargo.toml is at the root, build the entire workspace.
1361        Some(cargo) if cargo.path().parent() == cfg.path().parent() => build_all(
1362            &cfg,
1363            cfg.path(),
1364            no_idl,
1365            idl_out,
1366            idl_ts_out,
1367            &build_config,
1368            stdout,
1369            stderr,
1370            env_vars,
1371            cargo_args,
1372            skip_lint,
1373            no_docs,
1374            arch,
1375        )?,
1376        // Cargo.toml represents a single package. Build it.
1377        Some(cargo) => build_rust_cwd(
1378            &cfg,
1379            cargo.path().to_path_buf(),
1380            no_idl,
1381            idl_out,
1382            idl_ts_out,
1383            &build_config,
1384            stdout,
1385            stderr,
1386            env_vars,
1387            cargo_args,
1388            skip_lint,
1389            no_docs,
1390            &arch,
1391        )?,
1392    }
1393
1394    set_workspace_dir_or_exit();
1395
1396    Ok(())
1397}
1398
1399#[allow(clippy::too_many_arguments)]
1400fn build_all(
1401    cfg: &WithPath<Config>,
1402    cfg_path: &Path,
1403    no_idl: bool,
1404    idl_out: Option<PathBuf>,
1405    idl_ts_out: Option<PathBuf>,
1406    build_config: &BuildConfig,
1407    stdout: Option<File>, // Used for the package registry server.
1408    stderr: Option<File>, // Used for the package registry server.
1409    env_vars: Vec<String>,
1410    cargo_args: Vec<String>,
1411    skip_lint: bool,
1412    no_docs: bool,
1413    arch: ProgramArch,
1414) -> Result<()> {
1415    let cur_dir = std::env::current_dir()?;
1416    let r = match cfg_path.parent() {
1417        None => Err(anyhow!("Invalid Anchor.toml at {}", cfg_path.display())),
1418        Some(_parent) => {
1419            for p in cfg.get_rust_program_list()? {
1420                build_rust_cwd(
1421                    cfg,
1422                    p.join("Cargo.toml"),
1423                    no_idl,
1424                    idl_out.clone(),
1425                    idl_ts_out.clone(),
1426                    build_config,
1427                    stdout.as_ref().map(|f| f.try_clone()).transpose()?,
1428                    stderr.as_ref().map(|f| f.try_clone()).transpose()?,
1429                    env_vars.clone(),
1430                    cargo_args.clone(),
1431                    skip_lint,
1432                    no_docs,
1433                    &arch,
1434                )?;
1435            }
1436            Ok(())
1437        }
1438    };
1439    std::env::set_current_dir(cur_dir)?;
1440    r
1441}
1442
1443// Runs the build command outside of a workspace.
1444#[allow(clippy::too_many_arguments)]
1445fn build_rust_cwd(
1446    cfg: &WithPath<Config>,
1447    cargo_toml: PathBuf,
1448    no_idl: bool,
1449    idl_out: Option<PathBuf>,
1450    idl_ts_out: Option<PathBuf>,
1451    build_config: &BuildConfig,
1452    stdout: Option<File>,
1453    stderr: Option<File>,
1454    env_vars: Vec<String>,
1455    cargo_args: Vec<String>,
1456    skip_lint: bool,
1457    no_docs: bool,
1458    arch: &ProgramArch,
1459) -> Result<()> {
1460    match cargo_toml.parent() {
1461        None => return Err(anyhow!("Unable to find parent")),
1462        Some(p) => std::env::set_current_dir(p)?,
1463    };
1464    match build_config.verifiable {
1465        false => _build_rust_cwd(
1466            cfg, no_idl, idl_out, idl_ts_out, skip_lint, no_docs, arch, cargo_args,
1467        ),
1468        true => build_cwd_verifiable(
1469            cfg,
1470            cargo_toml,
1471            build_config,
1472            stdout,
1473            stderr,
1474            skip_lint,
1475            env_vars,
1476            cargo_args,
1477            no_docs,
1478            arch,
1479        ),
1480    }
1481}
1482
1483// Builds an anchor program in a docker image and copies the build artifacts
1484// into the `target/` directory.
1485#[allow(clippy::too_many_arguments)]
1486fn build_cwd_verifiable(
1487    cfg: &WithPath<Config>,
1488    cargo_toml: PathBuf,
1489    build_config: &BuildConfig,
1490    stdout: Option<File>,
1491    stderr: Option<File>,
1492    skip_lint: bool,
1493    env_vars: Vec<String>,
1494    cargo_args: Vec<String>,
1495    no_docs: bool,
1496    arch: &ProgramArch,
1497) -> Result<()> {
1498    // Create output dirs.
1499    let workspace_dir = cfg.path().parent().unwrap().canonicalize()?;
1500    let target_dir = workspace_dir.join("target");
1501    fs::create_dir_all(target_dir.join("verifiable"))?;
1502    fs::create_dir_all(target_dir.join("idl"))?;
1503    fs::create_dir_all(target_dir.join("types"))?;
1504    if !&cfg.workspace.types.is_empty() {
1505        fs::create_dir_all(workspace_dir.join(&cfg.workspace.types))?;
1506    }
1507
1508    let container_name = "anchor-program";
1509
1510    // Build the binary in docker.
1511    let result = docker_build(
1512        cfg,
1513        container_name,
1514        cargo_toml,
1515        build_config,
1516        stdout,
1517        stderr,
1518        env_vars,
1519        cargo_args.clone(),
1520        arch,
1521    );
1522
1523    match &result {
1524        Err(e) => {
1525            eprintln!("Error during Docker build: {e:?}");
1526        }
1527        Ok(_) => {
1528            // Build the idl.
1529            println!("Extracting the IDL");
1530            let idl = generate_idl(cfg, skip_lint, no_docs, &cargo_args)?;
1531            // Write out the JSON file.
1532            println!("Writing the IDL file");
1533            let out_file = workspace_dir
1534                .join("target")
1535                .join("idl")
1536                .join(&idl.metadata.name)
1537                .with_extension("json");
1538            write_idl(&idl, OutFile::File(out_file))?;
1539
1540            // Write out the TypeScript type.
1541            println!("Writing the .ts file");
1542            let ts_file = workspace_dir
1543                .join("target")
1544                .join("types")
1545                .join(&idl.metadata.name)
1546                .with_extension("ts");
1547            fs::write(&ts_file, idl_ts(&idl)?)?;
1548
1549            // Copy out the TypeScript type.
1550            if !&cfg.workspace.types.is_empty() {
1551                fs::copy(
1552                    ts_file,
1553                    workspace_dir
1554                        .join(&cfg.workspace.types)
1555                        .join(idl.metadata.name)
1556                        .with_extension("ts"),
1557                )?;
1558            }
1559
1560            println!("Build success");
1561        }
1562    }
1563
1564    result
1565}
1566
1567#[allow(clippy::too_many_arguments)]
1568fn docker_build(
1569    cfg: &WithPath<Config>,
1570    container_name: &str,
1571    cargo_toml: PathBuf,
1572    build_config: &BuildConfig,
1573    stdout: Option<File>,
1574    stderr: Option<File>,
1575    env_vars: Vec<String>,
1576    cargo_args: Vec<String>,
1577    arch: &ProgramArch,
1578) -> Result<()> {
1579    let binary_name = Manifest::from_path(&cargo_toml)?.lib_name()?;
1580
1581    // Docker vars.
1582    let workdir = Path::new("/workdir");
1583    let volume_mount = format!(
1584        "{}:{}",
1585        cfg.path().parent().unwrap().canonicalize()?.display(),
1586        workdir.to_str().unwrap(),
1587    );
1588    println!("Using image {:?}", build_config.docker_image);
1589
1590    // Start the docker image running detached in the background.
1591    let target_dir = workdir.join("docker-target");
1592    println!("Run docker image");
1593    let exit = std::process::Command::new("docker")
1594        .args([
1595            "run",
1596            "-it",
1597            "-d",
1598            "--name",
1599            container_name,
1600            "--env",
1601            &format!(
1602                "CARGO_TARGET_DIR={}",
1603                target_dir.as_path().to_str().unwrap()
1604            ),
1605            "-v",
1606            &volume_mount,
1607            "-w",
1608            workdir.to_str().unwrap(),
1609            &build_config.docker_image,
1610            "bash",
1611        ])
1612        .stdout(Stdio::inherit())
1613        .stderr(Stdio::inherit())
1614        .output()
1615        .map_err(|e| anyhow::format_err!("Docker build failed: {}", e.to_string()))?;
1616    if !exit.status.success() {
1617        return Err(anyhow!("Failed to build program"));
1618    }
1619
1620    let result = docker_prep(container_name, build_config).and_then(|_| {
1621        let cfg_parent = cfg.path().parent().unwrap();
1622        docker_build_bpf(
1623            container_name,
1624            cargo_toml.as_path(),
1625            cfg_parent,
1626            target_dir.as_path(),
1627            binary_name,
1628            stdout,
1629            stderr,
1630            env_vars,
1631            cargo_args,
1632            arch,
1633        )
1634    });
1635
1636    // Cleanup regardless of errors
1637    docker_cleanup(container_name, target_dir.as_path())?;
1638
1639    // Done.
1640    result
1641}
1642
1643fn docker_prep(container_name: &str, build_config: &BuildConfig) -> Result<()> {
1644    // Set the solana version in the container, if given. Otherwise use the
1645    // default.
1646    match build_config.bootstrap {
1647        BootstrapMode::Debian => {
1648            // Install build requirements
1649            docker_exec(container_name, &["apt", "update"])?;
1650            docker_exec(
1651                container_name,
1652                &["apt", "install", "-y", "curl", "build-essential"],
1653            )?;
1654
1655            // Install Rust
1656            docker_exec(
1657                container_name,
1658                &["curl", "https://sh.rustup.rs", "-sfo", "rustup.sh"],
1659            )?;
1660            docker_exec(container_name, &["sh", "rustup.sh", "-y"])?;
1661            docker_exec(container_name, &["rm", "-f", "rustup.sh"])?;
1662        }
1663        BootstrapMode::None => {}
1664    }
1665
1666    if let Some(solana_version) = &build_config.solana_version {
1667        println!("Using solana version: {solana_version}");
1668
1669        // Install Solana CLI
1670        docker_exec(
1671            container_name,
1672            &[
1673                "curl",
1674                "-sSfL",
1675                &format!("https://release.anza.xyz/v{solana_version}/install",),
1676                "-o",
1677                "solana_installer.sh",
1678            ],
1679        )?;
1680        docker_exec(container_name, &["sh", "solana_installer.sh"])?;
1681        docker_exec(container_name, &["rm", "-f", "solana_installer.sh"])?;
1682    }
1683    Ok(())
1684}
1685
1686#[allow(clippy::too_many_arguments)]
1687fn docker_build_bpf(
1688    container_name: &str,
1689    cargo_toml: &Path,
1690    cfg_parent: &Path,
1691    target_dir: &Path,
1692    binary_name: String,
1693    stdout: Option<File>,
1694    stderr: Option<File>,
1695    env_vars: Vec<String>,
1696    cargo_args: Vec<String>,
1697    arch: &ProgramArch,
1698) -> Result<()> {
1699    let manifest_path =
1700        pathdiff::diff_paths(cargo_toml.canonicalize()?, cfg_parent.canonicalize()?)
1701            .ok_or_else(|| anyhow!("Unable to diff paths"))?;
1702    println!(
1703        "Building {} manifest: {:?}",
1704        binary_name,
1705        manifest_path.display()
1706    );
1707
1708    let subcommand = arch.build_subcommand();
1709
1710    // Execute the build.
1711    let exit = std::process::Command::new("docker")
1712        .args([
1713            "exec",
1714            "--env",
1715            "PATH=/root/.local/share/solana/install/active_release/bin:/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
1716        ])
1717        .args(env_vars
1718            .iter()
1719            .map(|x| ["--env", x.as_str()])
1720            .collect::<Vec<[&str; 2]>>()
1721            .concat())
1722        .args([
1723            container_name,
1724            "cargo",
1725            subcommand,
1726            "--manifest-path",
1727            &manifest_path.display().to_string(),
1728        ])
1729        .args(cargo_args)
1730        .stdout(match stdout {
1731            None => Stdio::inherit(),
1732            Some(f) => f.into(),
1733        })
1734        .stderr(match stderr {
1735            None => Stdio::inherit(),
1736            Some(f) => f.into(),
1737        })
1738        .output()
1739        .map_err(|e| anyhow::format_err!("Docker build failed: {}", e.to_string()))?;
1740    if !exit.status.success() {
1741        return Err(anyhow!("Failed to build program"));
1742    }
1743
1744    // Copy the binary out of the docker image.
1745    println!("Copying out the build artifacts");
1746    let out_file = cfg_parent
1747        .canonicalize()?
1748        .join(
1749            Path::new("target")
1750                .join("verifiable")
1751                .join(&binary_name)
1752                .with_extension("so"),
1753        )
1754        .display()
1755        .to_string();
1756
1757    // This requires the target directory of any built program to be located at
1758    // the root of the workspace.
1759    let mut bin_path = target_dir.join("deploy");
1760    bin_path.push(format!("{binary_name}.so"));
1761    let bin_artifact = format!(
1762        "{}:{}",
1763        container_name,
1764        bin_path.as_path().to_str().unwrap()
1765    );
1766    let exit = std::process::Command::new("docker")
1767        .args(["cp", &bin_artifact, &out_file])
1768        .stdout(Stdio::inherit())
1769        .stderr(Stdio::inherit())
1770        .output()
1771        .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
1772    if !exit.status.success() {
1773        Err(anyhow!(
1774            "Failed to copy binary out of docker. Is the target directory set correctly?"
1775        ))
1776    } else {
1777        Ok(())
1778    }
1779}
1780
1781fn docker_cleanup(container_name: &str, target_dir: &Path) -> Result<()> {
1782    // Wipe the generated docker-target dir.
1783    println!("Cleaning up the docker target directory");
1784    docker_exec(container_name, &["rm", "-rf", target_dir.to_str().unwrap()])?;
1785
1786    // Remove the docker image.
1787    println!("Removing the docker container");
1788    let exit = std::process::Command::new("docker")
1789        .args(["rm", "-f", container_name])
1790        .stdout(Stdio::inherit())
1791        .stderr(Stdio::inherit())
1792        .output()
1793        .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
1794    if !exit.status.success() {
1795        println!("Unable to remove the docker container");
1796        std::process::exit(exit.status.code().unwrap_or(1));
1797    }
1798    Ok(())
1799}
1800
1801fn docker_exec(container_name: &str, args: &[&str]) -> Result<()> {
1802    let exit = std::process::Command::new("docker")
1803        .args([&["exec", container_name], args].concat())
1804        .stdout(Stdio::inherit())
1805        .stderr(Stdio::inherit())
1806        .output()
1807        .map_err(|e| anyhow!("Failed to run command \"{:?}\": {:?}", args, e))?;
1808    if !exit.status.success() {
1809        Err(anyhow!("Failed to run command: {:?}", args))
1810    } else {
1811        Ok(())
1812    }
1813}
1814
1815#[allow(clippy::too_many_arguments)]
1816fn _build_rust_cwd(
1817    cfg: &WithPath<Config>,
1818    no_idl: bool,
1819    idl_out: Option<PathBuf>,
1820    idl_ts_out: Option<PathBuf>,
1821    skip_lint: bool,
1822    no_docs: bool,
1823    arch: &ProgramArch,
1824    cargo_args: Vec<String>,
1825) -> Result<()> {
1826    let exit = std::process::Command::new("cargo")
1827        .arg(arch.build_subcommand())
1828        .args(cargo_args.clone())
1829        .stdout(Stdio::inherit())
1830        .stderr(Stdio::inherit())
1831        .output()
1832        .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
1833    if !exit.status.success() {
1834        std::process::exit(exit.status.code().unwrap_or(1));
1835    }
1836
1837    // Generate IDL
1838    if !no_idl {
1839        let idl = generate_idl(cfg, skip_lint, no_docs, &cargo_args)?;
1840
1841        // JSON out path.
1842        let out = match idl_out {
1843            None => PathBuf::from(".")
1844                .join(&idl.metadata.name)
1845                .with_extension("json"),
1846            Some(o) => PathBuf::from(&o.join(&idl.metadata.name).with_extension("json")),
1847        };
1848        // TS out path.
1849        let ts_out = match idl_ts_out {
1850            None => PathBuf::from(".")
1851                .join(&idl.metadata.name)
1852                .with_extension("ts"),
1853            Some(o) => PathBuf::from(&o.join(&idl.metadata.name).with_extension("ts")),
1854        };
1855
1856        // Write out the JSON file.
1857        write_idl(&idl, OutFile::File(out))?;
1858        // Write out the TypeScript type.
1859        fs::write(&ts_out, idl_ts(&idl)?)?;
1860
1861        // Copy out the TypeScript type.
1862        let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml");
1863        if !&cfg.workspace.types.is_empty() {
1864            fs::copy(
1865                &ts_out,
1866                cfg_parent
1867                    .join(&cfg.workspace.types)
1868                    .join(&idl.metadata.name)
1869                    .with_extension("ts"),
1870            )?;
1871        }
1872    }
1873
1874    Ok(())
1875}
1876
1877pub fn verify(
1878    program_id: Pubkey,
1879    repo_url: Option<String>,
1880    commit_hash: Option<String>,
1881    current_dir: bool,
1882    program_name: Option<String>,
1883    args: Vec<String>,
1884) -> Result<()> {
1885    let mut command_args = Vec::new();
1886
1887    match (current_dir, repo_url) {
1888        (true, _) => {
1889            let current_path = std::env::current_dir()?
1890                .to_str()
1891                .ok_or_else(|| anyhow!("Invalid current directory path"))?
1892                .to_owned();
1893            command_args.push(current_path);
1894            command_args.push("--current-dir".into());
1895        }
1896        (false, Some(url)) => {
1897            command_args.push(url);
1898        }
1899        (false, None) => {
1900            return Err(anyhow!(
1901                "You must provide either --repo-url or --current-dir"
1902            ));
1903        }
1904    }
1905
1906    if let Some(commit) = commit_hash {
1907        command_args.push("--commit-hash".into());
1908        command_args.push(commit);
1909    }
1910
1911    if let Some(name) = program_name {
1912        command_args.push("--library-name".into());
1913        command_args.push(name);
1914    }
1915
1916    command_args.push("--program-id".into());
1917    command_args.push(program_id.to_string());
1918
1919    command_args.extend(args);
1920
1921    println!("Verifying program {program_id}");
1922    let verify_path = AVM_HOME.join("bin").join("solana-verify");
1923    if !verify_path.exists() {
1924        install_with_avm(env!("CARGO_PKG_VERSION"), true)
1925            .context("installing Anchor with solana-verify")?;
1926    }
1927
1928    let status = std::process::Command::new(verify_path)
1929        .arg("verify-from-repo")
1930        .args(&command_args)
1931        .stdout(std::process::Stdio::inherit())
1932        .stderr(std::process::Stdio::inherit())
1933        .status()
1934        .with_context(|| "Failed to run `solana-verify`")?;
1935
1936    if !status.success() {
1937        return Err(anyhow!("Failed to verify program"));
1938    }
1939
1940    Ok(())
1941}
1942
1943fn cd_member(cfg_override: &ConfigOverride, program_name: &str) -> Result<()> {
1944    // Change directories to the given `program_name`, if given.
1945    let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
1946
1947    for program in cfg.read_all_programs()? {
1948        let cargo_toml = program.path.join("Cargo.toml");
1949        if !cargo_toml.exists() {
1950            return Err(anyhow!(
1951                "Did not find Cargo.toml at the path: {}",
1952                program.path.display()
1953            ));
1954        }
1955
1956        let manifest = Manifest::from_path(&cargo_toml)?;
1957        let pkg_name = manifest.package().name();
1958        let lib_name = manifest.lib_name()?;
1959        if program_name == pkg_name || program_name == lib_name {
1960            std::env::set_current_dir(&program.path)?;
1961            return Ok(());
1962        }
1963    }
1964
1965    Err(anyhow!("{} is not part of the workspace", program_name,))
1966}
1967
1968fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
1969    match subcmd {
1970        IdlCommand::Init {
1971            program_id,
1972            filepath,
1973            priority_fee,
1974        } => idl_init(cfg_override, program_id, filepath, priority_fee),
1975        IdlCommand::Close {
1976            program_id,
1977            idl_address,
1978            print_only,
1979            priority_fee,
1980        } => {
1981            let closed_address = idl_close(
1982                cfg_override,
1983                program_id,
1984                idl_address,
1985                print_only,
1986                priority_fee,
1987            )?;
1988            if !print_only {
1989                println!("Idl account closed: {closed_address}");
1990            }
1991            Ok(())
1992        }
1993        IdlCommand::WriteBuffer {
1994            program_id,
1995            filepath,
1996            priority_fee,
1997        } => {
1998            let idl_buffer = idl_write_buffer(cfg_override, program_id, filepath, priority_fee)?;
1999            println!("Idl buffer created: {idl_buffer}");
2000            Ok(())
2001        }
2002        IdlCommand::SetBuffer {
2003            program_id,
2004            buffer,
2005            print_only,
2006            priority_fee,
2007        } => idl_set_buffer(cfg_override, program_id, buffer, print_only, priority_fee).map(|_| ()),
2008        IdlCommand::Upgrade {
2009            program_id,
2010            filepath,
2011            priority_fee,
2012        } => idl_upgrade(cfg_override, program_id, filepath, priority_fee),
2013        IdlCommand::SetAuthority {
2014            program_id,
2015            address,
2016            new_authority,
2017            print_only,
2018            priority_fee,
2019        } => idl_set_authority(
2020            cfg_override,
2021            program_id,
2022            address,
2023            new_authority,
2024            print_only,
2025            priority_fee,
2026        ),
2027        IdlCommand::EraseAuthority {
2028            program_id,
2029            priority_fee,
2030        } => idl_erase_authority(cfg_override, program_id, priority_fee),
2031        IdlCommand::Authority { program_id } => idl_authority(cfg_override, program_id),
2032        IdlCommand::Build {
2033            program_name,
2034            out,
2035            out_ts,
2036            no_docs,
2037            skip_lint,
2038            cargo_args,
2039        } => idl_build(
2040            cfg_override,
2041            program_name,
2042            out,
2043            out_ts,
2044            no_docs,
2045            skip_lint,
2046            cargo_args,
2047        ),
2048        IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out),
2049        IdlCommand::Convert {
2050            path,
2051            out,
2052            program_id,
2053        } => idl_convert(path, out, program_id),
2054        IdlCommand::Type { path, out } => idl_type(path, out),
2055    }
2056}
2057
2058/// Fetch an IDL for the given program id.
2059///
2060/// Intentionally returns [`serde_json::Value`] rather than [`Idl`] to also support legacy IDLs.
2061fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result<serde_json::Value> {
2062    let url = match Config::discover(cfg_override)? {
2063        Some(cfg) => cluster_url(&cfg, &cfg.test_validator),
2064        None => {
2065            // If the command is not run inside a workspace,
2066            // cluster_url will be used from default solana config
2067            // provider.cluster option can be used to override this
2068            if let Some(cluster) = cfg_override.cluster.as_ref() {
2069                cluster.url().to_string()
2070            } else {
2071                config::get_solana_cfg_url()?
2072            }
2073        }
2074    };
2075
2076    let client = create_client(url);
2077
2078    let mut account = client.get_account(&idl_addr)?;
2079    if account.executable {
2080        let idl_addr = IdlAccount::address(&idl_addr);
2081        account = client.get_account(&idl_addr)?;
2082    }
2083
2084    // Cut off account discriminator.
2085    let mut d: &[u8] = &account.data[IdlAccount::DISCRIMINATOR.len()..];
2086    let idl_account: IdlAccount = AnchorDeserialize::deserialize(&mut d)?;
2087
2088    let compressed_len: usize = idl_account.data_len.try_into().unwrap();
2089    let compressed_bytes = &account.data[44..44 + compressed_len];
2090    let mut z = ZlibDecoder::new(compressed_bytes);
2091    let mut s = Vec::new();
2092    z.read_to_end(&mut s)?;
2093    serde_json::from_slice(&s[..]).map_err(Into::into)
2094}
2095
2096fn get_idl_account(client: &RpcClient, idl_address: &Pubkey) -> Result<IdlAccount> {
2097    let account = client.get_account(idl_address)?;
2098    let mut data: &[u8] = &account.data;
2099    AccountDeserialize::try_deserialize(&mut data).map_err(|e| anyhow!("{:?}", e))
2100}
2101
2102fn idl_init(
2103    cfg_override: &ConfigOverride,
2104    program_id: Pubkey,
2105    idl_filepath: String,
2106    priority_fee: Option<u64>,
2107) -> Result<()> {
2108    with_workspace(cfg_override, |cfg| {
2109        let keypair = cfg.provider.wallet.to_string();
2110
2111        let idl = fs::read(idl_filepath)?;
2112        let idl = convert_idl(&idl)?;
2113
2114        let idl_address = create_idl_account(cfg, &keypair, &program_id, &idl, priority_fee)?;
2115
2116        println!("Idl account created: {idl_address:?}");
2117        Ok(())
2118    })
2119}
2120
2121fn idl_close(
2122    cfg_override: &ConfigOverride,
2123    program_id: Pubkey,
2124    idl_address: Option<Pubkey>,
2125    print_only: bool,
2126    priority_fee: Option<u64>,
2127) -> Result<Pubkey> {
2128    with_workspace(cfg_override, |cfg| {
2129        let idl_address = idl_address.unwrap_or_else(|| IdlAccount::address(&program_id));
2130        idl_close_account(cfg, &program_id, idl_address, print_only, priority_fee)?;
2131
2132        Ok(idl_address)
2133    })
2134}
2135
2136fn idl_write_buffer(
2137    cfg_override: &ConfigOverride,
2138    program_id: Pubkey,
2139    idl_filepath: String,
2140    priority_fee: Option<u64>,
2141) -> Result<Pubkey> {
2142    with_workspace(cfg_override, |cfg| {
2143        let keypair = cfg.provider.wallet.to_string();
2144
2145        let idl = fs::read(idl_filepath)?;
2146        let idl = convert_idl(&idl)?;
2147
2148        let idl_buffer = create_idl_buffer(cfg, &keypair, &program_id, &idl, priority_fee)?;
2149        idl_write(cfg, &program_id, &idl, idl_buffer, priority_fee)?;
2150
2151        Ok(idl_buffer)
2152    })
2153}
2154
2155fn idl_set_buffer(
2156    cfg_override: &ConfigOverride,
2157    program_id: Pubkey,
2158    buffer: Pubkey,
2159    print_only: bool,
2160    priority_fee: Option<u64>,
2161) -> Result<Pubkey> {
2162    with_workspace(cfg_override, |cfg| {
2163        let keypair = get_keypair(&cfg.provider.wallet.to_string())?;
2164        let url = cluster_url(cfg, &cfg.test_validator);
2165        let client = create_client(url);
2166
2167        let idl_address = IdlAccount::address(&program_id);
2168        let idl_authority = if print_only {
2169            get_idl_account(&client, &idl_address)?.authority
2170        } else {
2171            keypair.pubkey()
2172        };
2173        // Instruction to set the buffer onto the IdlAccount.
2174        let ix = {
2175            let accounts = vec![
2176                AccountMeta::new(buffer, false),
2177                AccountMeta::new(idl_address, false),
2178                AccountMeta::new(idl_authority, true),
2179            ];
2180            let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
2181            data.append(&mut IdlInstruction::SetBuffer.try_to_vec()?);
2182            Instruction {
2183                program_id,
2184                accounts,
2185                data,
2186            }
2187        };
2188
2189        if print_only {
2190            print_idl_instruction("SetBuffer", &ix, &idl_address)?;
2191        } else {
2192            // Build the transaction.
2193            let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?;
2194
2195            // Send the transaction.
2196            let mut latest_hash = client.get_latest_blockhash()?;
2197            for retries in 0..20 {
2198                if !client.is_blockhash_valid(&latest_hash, client.commitment())? {
2199                    latest_hash = client.get_latest_blockhash()?;
2200                }
2201                let tx = Transaction::new_signed_with_payer(
2202                    &instructions,
2203                    Some(&keypair.pubkey()),
2204                    &[&keypair],
2205                    latest_hash,
2206                );
2207
2208                match client.send_and_confirm_transaction_with_spinner(&tx) {
2209                    Ok(_) => break,
2210                    Err(e) => {
2211                        if retries == 19 {
2212                            return Err(anyhow!("Error: {e}. Failed to send transaction."));
2213                        }
2214                        println!("Error: {e}. Retrying transaction.");
2215                    }
2216                }
2217            }
2218        }
2219
2220        Ok(idl_address)
2221    })
2222}
2223
2224fn idl_upgrade(
2225    cfg_override: &ConfigOverride,
2226    program_id: Pubkey,
2227    idl_filepath: String,
2228    priority_fee: Option<u64>,
2229) -> Result<()> {
2230    let buffer_address = idl_write_buffer(cfg_override, program_id, idl_filepath, priority_fee)?;
2231    let idl_address = idl_set_buffer(
2232        cfg_override,
2233        program_id,
2234        buffer_address,
2235        false,
2236        priority_fee,
2237    )?;
2238    idl_close(
2239        cfg_override,
2240        program_id,
2241        Some(buffer_address),
2242        false,
2243        priority_fee,
2244    )?;
2245    println!("Idl account {idl_address} successfully upgraded");
2246    Ok(())
2247}
2248
2249fn idl_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
2250    with_workspace(cfg_override, |cfg| {
2251        let url = cluster_url(cfg, &cfg.test_validator);
2252        let client = create_client(url);
2253        let idl_address = {
2254            let account = client.get_account(&program_id)?;
2255            if account.executable {
2256                IdlAccount::address(&program_id)
2257            } else {
2258                program_id
2259            }
2260        };
2261
2262        let idl_account = get_idl_account(&client, &idl_address)?;
2263
2264        println!("{:?}", idl_account.authority);
2265
2266        Ok(())
2267    })
2268}
2269
2270fn idl_set_authority(
2271    cfg_override: &ConfigOverride,
2272    program_id: Pubkey,
2273    address: Option<Pubkey>,
2274    new_authority: Pubkey,
2275    print_only: bool,
2276    priority_fee: Option<u64>,
2277) -> Result<()> {
2278    with_workspace(cfg_override, |cfg| {
2279        // Misc.
2280        let idl_address = match address {
2281            None => IdlAccount::address(&program_id),
2282            Some(addr) => addr,
2283        };
2284        let keypair = get_keypair(&cfg.provider.wallet.to_string())?;
2285        let url = cluster_url(cfg, &cfg.test_validator);
2286        let client = create_client(url);
2287
2288        let idl_authority = if print_only {
2289            get_idl_account(&client, &idl_address)?.authority
2290        } else {
2291            keypair.pubkey()
2292        };
2293
2294        // Instruction data.
2295        let data =
2296            serialize_idl_ix(anchor_lang::idl::IdlInstruction::SetAuthority { new_authority })?;
2297
2298        // Instruction accounts.
2299        let accounts = vec![
2300            AccountMeta::new(idl_address, false),
2301            AccountMeta::new_readonly(idl_authority, true),
2302        ];
2303
2304        // Instruction.
2305        let ix = Instruction {
2306            program_id,
2307            accounts,
2308            data,
2309        };
2310
2311        if print_only {
2312            print_idl_instruction("SetAuthority", &ix, &idl_address)?;
2313        } else {
2314            let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?;
2315
2316            // Send transaction.
2317            let latest_hash = client.get_latest_blockhash()?;
2318            let tx = Transaction::new_signed_with_payer(
2319                &instructions,
2320                Some(&keypair.pubkey()),
2321                &[&keypair],
2322                latest_hash,
2323            );
2324            client.send_and_confirm_transaction_with_spinner(&tx)?;
2325
2326            println!("Authority update complete.");
2327        }
2328
2329        Ok(())
2330    })
2331}
2332
2333fn idl_erase_authority(
2334    cfg_override: &ConfigOverride,
2335    program_id: Pubkey,
2336    priority_fee: Option<u64>,
2337) -> Result<()> {
2338    println!("Are you sure you want to erase the IDL authority: [y/n]");
2339
2340    let stdin = std::io::stdin();
2341    let mut stdin_lines = stdin.lock().lines();
2342    let input = stdin_lines.next().unwrap().unwrap();
2343    if input != "y" {
2344        println!("Not erasing.");
2345        return Ok(());
2346    }
2347
2348    idl_set_authority(
2349        cfg_override,
2350        program_id,
2351        None,
2352        ERASED_AUTHORITY,
2353        false,
2354        priority_fee,
2355    )?;
2356
2357    Ok(())
2358}
2359
2360fn idl_close_account(
2361    cfg: &Config,
2362    program_id: &Pubkey,
2363    idl_address: Pubkey,
2364    print_only: bool,
2365    priority_fee: Option<u64>,
2366) -> Result<()> {
2367    let keypair = get_keypair(&cfg.provider.wallet.to_string())?;
2368    let url = cluster_url(cfg, &cfg.test_validator);
2369    let client = create_client(url);
2370
2371    let idl_authority = if print_only {
2372        get_idl_account(&client, &idl_address)?.authority
2373    } else {
2374        keypair.pubkey()
2375    };
2376    // Instruction accounts.
2377    let accounts = vec![
2378        AccountMeta::new(idl_address, false),
2379        AccountMeta::new_readonly(idl_authority, true),
2380        AccountMeta::new(keypair.pubkey(), false),
2381    ];
2382    // Instruction.
2383    let ix = Instruction {
2384        program_id: *program_id,
2385        accounts,
2386        data: { serialize_idl_ix(anchor_lang::idl::IdlInstruction::Close {})? },
2387    };
2388
2389    if print_only {
2390        print_idl_instruction("Close", &ix, &idl_address)?;
2391    } else {
2392        let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?;
2393
2394        // Send transaction.
2395        let latest_hash = client.get_latest_blockhash()?;
2396        let tx = Transaction::new_signed_with_payer(
2397            &instructions,
2398            Some(&keypair.pubkey()),
2399            &[&keypair],
2400            latest_hash,
2401        );
2402        client.send_and_confirm_transaction_with_spinner(&tx)?;
2403    }
2404
2405    Ok(())
2406}
2407
2408// Write the idl to the account buffer, chopping up the IDL into pieces
2409// and sending multiple transactions in the event the IDL doesn't fit into
2410// a single transaction.
2411fn idl_write(
2412    cfg: &Config,
2413    program_id: &Pubkey,
2414    idl: &Idl,
2415    idl_address: Pubkey,
2416    priority_fee: Option<u64>,
2417) -> Result<()> {
2418    // Misc.
2419    let keypair = get_keypair(&cfg.provider.wallet.to_string())?;
2420    let url = cluster_url(cfg, &cfg.test_validator);
2421    let client = create_client(url);
2422
2423    // Serialize and compress the idl.
2424    let idl_data = {
2425        let json_bytes = serde_json::to_vec(idl)?;
2426        let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
2427        e.write_all(&json_bytes)?;
2428        e.finish()?
2429    };
2430
2431    println!("Idl data length: {:?} bytes", idl_data.len());
2432
2433    const MAX_WRITE_SIZE: usize = 600;
2434    let mut offset = 0;
2435    while offset < idl_data.len() {
2436        println!("Step {offset}/{} ", idl_data.len());
2437        // Instruction data.
2438        let data = {
2439            let start = offset;
2440            let end = std::cmp::min(offset + MAX_WRITE_SIZE, idl_data.len());
2441            serialize_idl_ix(anchor_lang::idl::IdlInstruction::Write {
2442                data: idl_data[start..end].to_vec(),
2443            })?
2444        };
2445        // Instruction accounts.
2446        let accounts = vec![
2447            AccountMeta::new(idl_address, false),
2448            AccountMeta::new_readonly(keypair.pubkey(), true),
2449        ];
2450        // Instruction.
2451        let ix = Instruction {
2452            program_id: *program_id,
2453            accounts,
2454            data,
2455        };
2456        // Send transaction.
2457        let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?;
2458
2459        let mut latest_hash = client.get_latest_blockhash()?;
2460        for retries in 0..20 {
2461            if !client.is_blockhash_valid(&latest_hash, client.commitment())? {
2462                latest_hash = client.get_latest_blockhash()?;
2463            }
2464            let tx = Transaction::new_signed_with_payer(
2465                &instructions,
2466                Some(&keypair.pubkey()),
2467                &[&keypair],
2468                latest_hash,
2469            );
2470
2471            match client.send_and_confirm_transaction_with_spinner(&tx) {
2472                Ok(_) => break,
2473                Err(e) => {
2474                    if retries == 19 {
2475                        return Err(anyhow!("Error: {e}. Failed to send transaction."));
2476                    }
2477                    println!("Error: {e}. Retrying transaction.");
2478                }
2479            }
2480        }
2481
2482        offset += MAX_WRITE_SIZE;
2483    }
2484    Ok(())
2485}
2486
2487fn idl_build(
2488    cfg_override: &ConfigOverride,
2489    program_name: Option<String>,
2490    out: Option<String>,
2491    out_ts: Option<String>,
2492    no_docs: bool,
2493    skip_lint: bool,
2494    cargo_args: Vec<String>,
2495) -> Result<()> {
2496    let cfg = Config::discover(cfg_override)?.expect("Not in workspace");
2497    let current_dir = std::env::current_dir()?;
2498    let program_path = match program_name {
2499        Some(name) => cfg.get_program(&name)?.path,
2500        None => {
2501            let programs = cfg.read_all_programs()?;
2502            if programs.len() == 1 {
2503                programs.into_iter().next().unwrap().path
2504            } else {
2505                programs
2506                    .into_iter()
2507                    .find(|program| program.path == current_dir)
2508                    .ok_or_else(|| anyhow!("Not in a program directory"))?
2509                    .path
2510            }
2511        }
2512    };
2513    std::env::set_current_dir(program_path)?;
2514    let idl = generate_idl(&cfg, skip_lint, no_docs, &cargo_args)?;
2515    std::env::set_current_dir(current_dir)?;
2516
2517    let out = match out {
2518        Some(path) => OutFile::File(PathBuf::from(path)),
2519        None => OutFile::Stdout,
2520    };
2521    write_idl(&idl, out)?;
2522
2523    if let Some(path) = out_ts {
2524        fs::write(path, idl_ts(&idl)?)?;
2525    }
2526
2527    Ok(())
2528}
2529
2530/// Generate IDL with method decided by whether manifest file has `idl-build` feature or not.
2531fn generate_idl(
2532    cfg: &WithPath<Config>,
2533    skip_lint: bool,
2534    no_docs: bool,
2535    cargo_args: &[String],
2536) -> Result<Idl> {
2537    check_idl_build_feature()?;
2538
2539    anchor_lang_idl::build::IdlBuilder::new()
2540        .resolution(cfg.features.resolution)
2541        .skip_lint(cfg.features.skip_lint || skip_lint)
2542        .no_docs(no_docs)
2543        .cargo_args(cargo_args.into())
2544        .build()
2545}
2546
2547fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option<String>) -> Result<()> {
2548    let idl = fetch_idl(cfg_override, address).map(|idl| serde_json::to_string_pretty(&idl))??;
2549    match out {
2550        Some(out) => fs::write(out, idl)?,
2551        _ => println!("{idl}"),
2552    };
2553
2554    Ok(())
2555}
2556
2557fn idl_convert(path: String, out: Option<String>, program_id: Option<Pubkey>) -> Result<()> {
2558    let idl = fs::read(path)?;
2559
2560    // Set the `metadata.address` field based on the given `program_id`
2561    let idl = match program_id {
2562        Some(program_id) => {
2563            let mut idl = serde_json::from_slice::<serde_json::Value>(&idl)?;
2564            idl.as_object_mut()
2565                .ok_or_else(|| anyhow!("IDL must be an object"))?
2566                .insert(
2567                    "metadata".into(),
2568                    serde_json::json!({ "address": program_id.to_string() }),
2569                );
2570            serde_json::to_vec(&idl)?
2571        }
2572        _ => idl,
2573    };
2574
2575    let idl = convert_idl(&idl)?;
2576    let out = match out {
2577        None => OutFile::Stdout,
2578        Some(out) => OutFile::File(PathBuf::from(out)),
2579    };
2580    write_idl(&idl, out)
2581}
2582
2583fn idl_type(path: String, out: Option<String>) -> Result<()> {
2584    let idl = fs::read(path)?;
2585    let idl = convert_idl(&idl)?;
2586    let types = idl_ts(&idl)?;
2587    match out {
2588        Some(out) => fs::write(out, types)?,
2589        _ => println!("{types}"),
2590    };
2591    Ok(())
2592}
2593
2594fn idl_ts(idl: &Idl) -> Result<String> {
2595    let idl_name = &idl.metadata.name;
2596    let type_name = idl_name.to_pascal_case();
2597    let idl = serde_json::to_string(idl)?;
2598
2599    // Convert every field of the IDL to camelCase
2600    let camel_idl = Regex::new(r#""\w+":"([\w\d]+)""#)?
2601        .captures_iter(&idl)
2602        .fold(idl.clone(), |acc, cur| {
2603            let name = cur.get(1).unwrap().as_str();
2604
2605            // Do not modify pubkeys
2606            if Pubkey::from_str(name).is_ok() {
2607                return acc;
2608            }
2609
2610            let camel_name = name.to_lower_camel_case();
2611            acc.replace(&format!(r#""{name}""#), &format!(r#""{camel_name}""#))
2612        });
2613
2614    // Pretty format
2615    let camel_idl = serde_json::to_string_pretty(&serde_json::from_str::<Idl>(&camel_idl)?)?;
2616
2617    Ok(format!(
2618        r#"/**
2619 * Program IDL in camelCase format in order to be used in JS/TS.
2620 *
2621 * Note that this is only a type helper and is not the actual IDL. The original
2622 * IDL can be found at `target/idl/{idl_name}.json`.
2623 */
2624export type {type_name} = {camel_idl};
2625"#
2626    ))
2627}
2628
2629fn write_idl(idl: &Idl, out: OutFile) -> Result<()> {
2630    let idl_json = serde_json::to_string_pretty(idl)?;
2631    match out {
2632        OutFile::Stdout => println!("{idl_json}"),
2633        OutFile::File(out) => fs::write(out, idl_json)?,
2634    };
2635
2636    Ok(())
2637}
2638
2639/// Print `base64+borsh` encoded IDL instruction.
2640fn print_idl_instruction(ix_name: &str, ix: &Instruction, idl_address: &Pubkey) -> Result<()> {
2641    use base64::engine::general_purpose::STANDARD;
2642    use base64::Engine;
2643
2644    println!("Print only mode. No execution!");
2645    println!("Instruction: {ix_name}");
2646    println!("IDL address: {idl_address}");
2647    println!("Program: {}", ix.program_id);
2648
2649    // Serialize with `bincode` because `Instruction` does not implement `BorshSerialize`
2650    let mut serialized_ix = bincode::serialize(ix)?;
2651
2652    // Remove extra bytes in order to make the serialized instruction `borsh` compatible
2653    // `bincode` uses 8 bytes(LE) for length meanwhile `borsh` uses 4 bytes(LE)
2654    let mut remove_extra_vec_bytes = |index: usize| {
2655        serialized_ix.drain((index + 4)..(index + 8));
2656    };
2657
2658    let accounts_index = std::mem::size_of_val(&ix.program_id);
2659    remove_extra_vec_bytes(accounts_index);
2660    let data_index = accounts_index + 4 + std::mem::size_of_val(&*ix.accounts);
2661    remove_extra_vec_bytes(data_index);
2662
2663    println!(
2664        "Base64 encoded instruction: {}",
2665        STANDARD.encode(serialized_ix)
2666    );
2667
2668    Ok(())
2669}
2670
2671fn account(
2672    cfg_override: &ConfigOverride,
2673    account_type: String,
2674    address: Pubkey,
2675    idl_filepath: Option<String>,
2676) -> Result<()> {
2677    let (program_name, account_type_name) = account_type
2678        .split_once('.') // Split at first occurrence of dot
2679        .and_then(|(x, y)| y.find('.').map_or_else(|| Some((x, y)), |_| None)) // ensures no dots in second substring
2680        .ok_or_else(|| {
2681            anyhow!(
2682                "Please enter the account struct in the following format: <program_name>.<Account>",
2683            )
2684        })?;
2685
2686    let idl = idl_filepath.map_or_else(
2687        || {
2688            Config::discover(cfg_override)
2689                .expect("Error when detecting workspace.")
2690                .expect("Not in workspace.")
2691                .read_all_programs()
2692                .expect("Workspace must contain atleast one program.")
2693                .into_iter()
2694                .find(|p| p.lib_name == *program_name)
2695                .ok_or_else(|| anyhow!("Program {program_name} not found in workspace."))
2696                .map(|p| p.idl)?
2697                .ok_or_else(|| {
2698                    anyhow!(
2699                        "IDL not found. Please build the program atleast once to generate the IDL."
2700                    )
2701                })
2702        },
2703        |idl_path| {
2704            let idl = fs::read(idl_path)?;
2705            let idl = convert_idl(&idl)?;
2706            if idl.metadata.name != program_name {
2707                return Err(anyhow!("IDL does not match program {program_name}."));
2708            }
2709
2710            Ok(idl)
2711        },
2712    )?;
2713
2714    let cluster = match &cfg_override.cluster {
2715        Some(cluster) => cluster.clone(),
2716        None => Config::discover(cfg_override)?
2717            .map(|cfg| cfg.provider.cluster.clone())
2718            .unwrap_or(Cluster::Localnet),
2719    };
2720
2721    let data = create_client(cluster.url()).get_account_data(&address)?;
2722    let disc_len = idl
2723        .accounts
2724        .iter()
2725        .find(|acc| acc.name == account_type_name)
2726        .map(|acc| acc.discriminator.len())
2727        .ok_or_else(|| anyhow!("Account `{account_type_name}` not found in IDL"))?;
2728    let mut data_view = &data[disc_len..];
2729
2730    let deserialized_json =
2731        deserialize_idl_defined_type_to_json(&idl, account_type_name, &mut data_view)?;
2732
2733    println!(
2734        "{}",
2735        serde_json::to_string_pretty(&deserialized_json).unwrap()
2736    );
2737
2738    Ok(())
2739}
2740
2741// Deserializes user defined IDL types by munching the account data(recursively).
2742fn deserialize_idl_defined_type_to_json(
2743    idl: &Idl,
2744    defined_type_name: &str,
2745    data: &mut &[u8],
2746) -> Result<JsonValue, anyhow::Error> {
2747    let defined_type = &idl
2748        .accounts
2749        .iter()
2750        .find(|acc| acc.name == defined_type_name)
2751        .and_then(|acc| idl.types.iter().find(|ty| ty.name == acc.name))
2752        .or_else(|| idl.types.iter().find(|ty| ty.name == defined_type_name))
2753        .ok_or_else(|| anyhow!("Type `{}` not found in IDL.", defined_type_name))?
2754        .ty;
2755
2756    let mut deserialized_fields = Map::new();
2757
2758    match defined_type {
2759        IdlTypeDefTy::Struct { fields } => {
2760            if let Some(fields) = fields {
2761                match fields {
2762                    IdlDefinedFields::Named(fields) => {
2763                        for field in fields {
2764                            deserialized_fields.insert(
2765                                field.name.clone(),
2766                                deserialize_idl_type_to_json(&field.ty, data, idl)?,
2767                            );
2768                        }
2769                    }
2770                    IdlDefinedFields::Tuple(fields) => {
2771                        let mut values = Vec::new();
2772                        for field in fields {
2773                            values.push(deserialize_idl_type_to_json(field, data, idl)?);
2774                        }
2775                        deserialized_fields
2776                            .insert(defined_type_name.to_owned(), JsonValue::Array(values));
2777                    }
2778                }
2779            }
2780        }
2781        IdlTypeDefTy::Enum { variants } => {
2782            let repr = <u8 as AnchorDeserialize>::deserialize(data)?;
2783
2784            let variant = variants
2785                .get(repr as usize)
2786                .ok_or_else(|| anyhow!("Error while deserializing enum variant {repr}"))?;
2787
2788            let mut value = json!({});
2789
2790            if let Some(enum_field) = &variant.fields {
2791                match enum_field {
2792                    IdlDefinedFields::Named(fields) => {
2793                        let mut values = Map::new();
2794                        for field in fields {
2795                            values.insert(
2796                                field.name.clone(),
2797                                deserialize_idl_type_to_json(&field.ty, data, idl)?,
2798                            );
2799                        }
2800                        value = JsonValue::Object(values);
2801                    }
2802                    IdlDefinedFields::Tuple(fields) => {
2803                        let mut values = Vec::new();
2804                        for field in fields {
2805                            values.push(deserialize_idl_type_to_json(field, data, idl)?);
2806                        }
2807                        value = JsonValue::Array(values);
2808                    }
2809                }
2810            }
2811
2812            deserialized_fields.insert(variant.name.clone(), value);
2813        }
2814        IdlTypeDefTy::Type { alias } => {
2815            return deserialize_idl_type_to_json(alias, data, idl);
2816        }
2817    }
2818
2819    Ok(JsonValue::Object(deserialized_fields))
2820}
2821
2822// Deserializes a primitive type using AnchorDeserialize
2823fn deserialize_idl_type_to_json(
2824    idl_type: &IdlType,
2825    data: &mut &[u8],
2826    parent_idl: &Idl,
2827) -> Result<JsonValue, anyhow::Error> {
2828    if data.is_empty() {
2829        return Err(anyhow::anyhow!("Unable to parse from empty bytes"));
2830    }
2831
2832    Ok(match idl_type {
2833        IdlType::Bool => json!(<bool as AnchorDeserialize>::deserialize(data)?),
2834        IdlType::U8 => {
2835            json!(<u8 as AnchorDeserialize>::deserialize(data)?)
2836        }
2837        IdlType::I8 => {
2838            json!(<i8 as AnchorDeserialize>::deserialize(data)?)
2839        }
2840        IdlType::U16 => {
2841            json!(<u16 as AnchorDeserialize>::deserialize(data)?)
2842        }
2843        IdlType::I16 => {
2844            json!(<i16 as AnchorDeserialize>::deserialize(data)?)
2845        }
2846        IdlType::U32 => {
2847            json!(<u32 as AnchorDeserialize>::deserialize(data)?)
2848        }
2849        IdlType::I32 => {
2850            json!(<i32 as AnchorDeserialize>::deserialize(data)?)
2851        }
2852        IdlType::F32 => json!(<f32 as AnchorDeserialize>::deserialize(data)?),
2853        IdlType::U64 => {
2854            json!(<u64 as AnchorDeserialize>::deserialize(data)?)
2855        }
2856        IdlType::I64 => {
2857            json!(<i64 as AnchorDeserialize>::deserialize(data)?)
2858        }
2859        IdlType::F64 => json!(<f64 as AnchorDeserialize>::deserialize(data)?),
2860        IdlType::U128 => {
2861            // TODO: Remove to_string once serde_json supports u128 deserialization
2862            json!(<u128 as AnchorDeserialize>::deserialize(data)?.to_string())
2863        }
2864        IdlType::I128 => {
2865            // TODO: Remove to_string once serde_json supports i128 deserialization
2866            json!(<i128 as AnchorDeserialize>::deserialize(data)?.to_string())
2867        }
2868        IdlType::U256 => todo!("Upon completion of u256 IDL standard"),
2869        IdlType::I256 => todo!("Upon completion of i256 IDL standard"),
2870        IdlType::Bytes => JsonValue::Array(
2871            <Vec<u8> as AnchorDeserialize>::deserialize(data)?
2872                .iter()
2873                .map(|i| json!(*i))
2874                .collect(),
2875        ),
2876        IdlType::String => json!(<String as AnchorDeserialize>::deserialize(data)?),
2877        IdlType::Pubkey => {
2878            json!(<Pubkey as AnchorDeserialize>::deserialize(data)?.to_string())
2879        }
2880        IdlType::Array(ty, size) => match size {
2881            IdlArrayLen::Value(size) => {
2882                let mut array_data: Vec<JsonValue> = Vec::with_capacity(*size);
2883
2884                for _ in 0..*size {
2885                    array_data.push(deserialize_idl_type_to_json(ty, data, parent_idl)?);
2886                }
2887
2888                JsonValue::Array(array_data)
2889            }
2890            // TODO:
2891            IdlArrayLen::Generic(_) => unimplemented!("Generic array length is not yet supported"),
2892        },
2893        IdlType::Option(ty) => {
2894            let is_present = <u8 as AnchorDeserialize>::deserialize(data)?;
2895
2896            if is_present == 0 {
2897                JsonValue::String("None".to_string())
2898            } else {
2899                deserialize_idl_type_to_json(ty, data, parent_idl)?
2900            }
2901        }
2902        IdlType::Vec(ty) => {
2903            let size: usize = <u32 as AnchorDeserialize>::deserialize(data)?
2904                .try_into()
2905                .unwrap();
2906
2907            let mut vec_data: Vec<JsonValue> = Vec::with_capacity(size);
2908
2909            for _ in 0..size {
2910                vec_data.push(deserialize_idl_type_to_json(ty, data, parent_idl)?);
2911            }
2912
2913            JsonValue::Array(vec_data)
2914        }
2915        IdlType::Defined {
2916            name,
2917            generics: _generics,
2918        } => {
2919            // TODO: Generics
2920            deserialize_idl_defined_type_to_json(parent_idl, name, data)?
2921        }
2922        IdlType::Generic(generic) => json!(generic),
2923        _ => unimplemented!("{idl_type:?}"),
2924    })
2925}
2926
2927enum OutFile {
2928    Stdout,
2929    File(PathBuf),
2930}
2931
2932// Builds, deploys, and tests all workspace programs in a single command.
2933#[allow(clippy::too_many_arguments)]
2934fn test(
2935    cfg_override: &ConfigOverride,
2936    program_name: Option<String>,
2937    skip_deploy: bool,
2938    skip_local_validator: bool,
2939    skip_build: bool,
2940    skip_lint: bool,
2941    no_idl: bool,
2942    detach: bool,
2943    tests_to_run: Vec<String>,
2944    extra_args: Vec<String>,
2945    env_vars: Vec<String>,
2946    cargo_args: Vec<String>,
2947    arch: ProgramArch,
2948) -> Result<()> {
2949    let test_paths = tests_to_run
2950        .iter()
2951        .map(|path| {
2952            PathBuf::from(path)
2953                .canonicalize()
2954                .map_err(|_| anyhow!("Wrong path {}", path))
2955        })
2956        .collect::<Result<Vec<_>, _>>()?;
2957
2958    with_workspace(cfg_override, |cfg| {
2959        // Build if needed.
2960        if !skip_build {
2961            build(
2962                cfg_override,
2963                no_idl,
2964                None,
2965                None,
2966                false,
2967                skip_lint,
2968                program_name.clone(),
2969                None,
2970                None,
2971                BootstrapMode::None,
2972                None,
2973                None,
2974                env_vars,
2975                cargo_args,
2976                false,
2977                arch,
2978            )?;
2979        }
2980
2981        let root = cfg.path().parent().unwrap().to_owned();
2982        cfg.add_test_config(root, test_paths)?;
2983
2984        // Run the deploy against the cluster in two cases:
2985        //
2986        // 1. The cluster is not localnet.
2987        // 2. The cluster is localnet, but we're not booting a local validator.
2988        //
2989        // In either case, skip the deploy if the user specifies.
2990        let is_localnet = cfg.provider.cluster == Cluster::Localnet;
2991        if (!is_localnet || skip_local_validator) && !skip_deploy {
2992            deploy(cfg_override, None, None, false, true, vec![])?;
2993        }
2994        let mut is_first_suite = true;
2995        if let Some(test_script) = cfg.scripts.get_mut("test") {
2996            is_first_suite = false;
2997
2998            match program_name {
2999                Some(program_name) => {
3000                    if let Some((from, to)) = Regex::new("\\s(tests/\\S+\\.(js|ts))")
3001                        .unwrap()
3002                        .captures_iter(&test_script.clone())
3003                        .last()
3004                        .and_then(|c| c.get(1).and_then(|mtch| c.get(2).map(|ext| (mtch, ext))))
3005                        .map(|(mtch, ext)| {
3006                            (
3007                                mtch.as_str(),
3008                                format!("tests/{program_name}.{}", ext.as_str()),
3009                            )
3010                        })
3011                    {
3012                        println!("\nRunning tests of program `{program_name}`!");
3013                        // Replace the last path to the program name's path
3014                        *test_script = test_script.replace(from, &to);
3015                    }
3016                }
3017                _ => println!(
3018                    "\nFound a 'test' script in the Anchor.toml. Running it as a test suite!"
3019                ),
3020            }
3021
3022            run_test_suite(
3023                cfg,
3024                cfg.path(),
3025                is_localnet,
3026                skip_local_validator,
3027                skip_deploy,
3028                detach,
3029                &cfg.test_validator,
3030                &cfg.scripts,
3031                &extra_args,
3032            )?;
3033        }
3034        if let Some(test_config) = &cfg.test_config {
3035            for test_suite in test_config.iter() {
3036                if !is_first_suite {
3037                    std::thread::sleep(std::time::Duration::from_millis(
3038                        test_suite
3039                            .1
3040                            .test
3041                            .as_ref()
3042                            .map(|val| val.shutdown_wait)
3043                            .unwrap_or(SHUTDOWN_WAIT) as u64,
3044                    ));
3045                } else {
3046                    is_first_suite = false;
3047                }
3048
3049                run_test_suite(
3050                    cfg,
3051                    test_suite.0,
3052                    is_localnet,
3053                    skip_local_validator,
3054                    skip_deploy,
3055                    detach,
3056                    &test_suite.1.test,
3057                    &test_suite.1.scripts,
3058                    &extra_args,
3059                )?;
3060            }
3061        }
3062        Ok(())
3063    })
3064}
3065
3066#[allow(clippy::too_many_arguments)]
3067fn run_test_suite(
3068    cfg: &WithPath<Config>,
3069    test_suite_path: impl AsRef<Path>,
3070    is_localnet: bool,
3071    skip_local_validator: bool,
3072    skip_deploy: bool,
3073    detach: bool,
3074    test_validator: &Option<TestValidator>,
3075    scripts: &ScriptsConfig,
3076    extra_args: &[String],
3077) -> Result<()> {
3078    println!("\nRunning test suite: {:#?}\n", test_suite_path.as_ref());
3079    // Start local test validator, if needed.
3080    let mut validator_handle = None;
3081    if is_localnet && (!skip_local_validator) {
3082        let flags = match skip_deploy {
3083            true => None,
3084            false => Some(validator_flags(cfg, test_validator)?),
3085        };
3086        validator_handle = Some(start_test_validator(cfg, test_validator, flags, true)?);
3087    }
3088
3089    let url = cluster_url(cfg, test_validator);
3090
3091    let node_options = format!(
3092        "{} {}",
3093        match std::env::var_os("NODE_OPTIONS") {
3094            Some(value) => value
3095                .into_string()
3096                .map_err(std::env::VarError::NotUnicode)?,
3097            None => "".to_owned(),
3098        },
3099        get_node_dns_option()?,
3100    );
3101
3102    // Setup log reader.
3103    let log_streams = stream_logs(cfg, &url);
3104
3105    // Run the tests.
3106    let test_result = {
3107        let cmd = scripts
3108            .get("test")
3109            .expect("Not able to find script for `test`")
3110            .clone();
3111        let script_args = format!("{cmd} {}", extra_args.join(" "));
3112        std::process::Command::new("bash")
3113            .arg("-c")
3114            .arg(script_args)
3115            .env("ANCHOR_PROVIDER_URL", url)
3116            .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
3117            .env("NODE_OPTIONS", node_options)
3118            .stdout(Stdio::inherit())
3119            .stderr(Stdio::inherit())
3120            .output()
3121            .map_err(anyhow::Error::from)
3122            .context(cmd)
3123    };
3124
3125    // Keep validator running if needed.
3126    if test_result.is_ok() && detach {
3127        println!("Local validator still running. Press Ctrl + C quit.");
3128        std::io::stdin().lock().lines().next().unwrap().unwrap();
3129    }
3130
3131    // Check all errors and shut down.
3132    if let Some(mut child) = validator_handle {
3133        if let Err(err) = child.kill() {
3134            println!("Failed to kill subprocess {}: {}", child.id(), err);
3135        }
3136    }
3137    for mut child in log_streams? {
3138        if let Err(err) = child.kill() {
3139            println!("Failed to kill subprocess {}: {}", child.id(), err);
3140        }
3141    }
3142
3143    // Must exist *after* shutting down the validator and log streams.
3144    match test_result {
3145        Ok(exit) => {
3146            if !exit.status.success() {
3147                std::process::exit(exit.status.code().unwrap());
3148            }
3149        }
3150        Err(err) => {
3151            println!("Failed to run test: {err:#}");
3152            return Err(err);
3153        }
3154    }
3155
3156    Ok(())
3157}
3158
3159// Returns the solana-test-validator flags. This will embed the workspace
3160// programs in the genesis block so we don't have to deploy every time. It also
3161// allows control of other solana-test-validator features.
3162fn validator_flags(
3163    cfg: &WithPath<Config>,
3164    test_validator: &Option<TestValidator>,
3165) -> Result<Vec<String>> {
3166    let programs = cfg.programs.get(&Cluster::Localnet);
3167
3168    let test_upgradeable_program = test_validator
3169        .as_ref()
3170        .map(|test_validator| test_validator.upgradeable)
3171        .unwrap_or(false);
3172
3173    let mut flags = Vec::new();
3174    for mut program in cfg.read_all_programs()? {
3175        let verifiable = false;
3176        let binary_path = program.binary_path(verifiable).display().to_string();
3177
3178        // Use the [programs.cluster] override and fallback to the keypair
3179        // files if no override is given.
3180        let address = programs
3181            .and_then(|m| m.get(&program.lib_name))
3182            .map(|deployment| Ok(deployment.address.to_string()))
3183            .unwrap_or_else(|| program.pubkey().map(|p| p.to_string()))?;
3184
3185        if test_upgradeable_program {
3186            flags.push("--upgradeable-program".to_string());
3187            flags.push(address.clone());
3188            flags.push(binary_path);
3189            flags.push(cfg.wallet_kp()?.pubkey().to_string());
3190        } else {
3191            flags.push("--bpf-program".to_string());
3192            flags.push(address.clone());
3193            flags.push(binary_path);
3194        }
3195
3196        if let Some(idl) = program.idl.as_mut() {
3197            // Add program address to the IDL.
3198            idl.address = address;
3199
3200            // Persist it.
3201            let idl_out = Path::new("target")
3202                .join("idl")
3203                .join(&idl.metadata.name)
3204                .with_extension("json");
3205            write_idl(idl, OutFile::File(idl_out))?;
3206        }
3207    }
3208
3209    if let Some(test) = test_validator.as_ref() {
3210        if let Some(genesis) = &test.genesis {
3211            for entry in genesis {
3212                let program_path = Path::new(&entry.program);
3213                if !program_path.exists() {
3214                    return Err(anyhow!(
3215                        "Program in genesis configuration does not exist at path: {}",
3216                        program_path.display()
3217                    ));
3218                }
3219                if entry.upgradeable.unwrap_or(false) {
3220                    flags.push("--upgradeable-program".to_string());
3221                    flags.push(entry.address.clone());
3222                    flags.push(entry.program.clone());
3223                    flags.push(cfg.wallet_kp()?.pubkey().to_string());
3224                } else {
3225                    flags.push("--bpf-program".to_string());
3226                    flags.push(entry.address.clone());
3227                    flags.push(entry.program.clone());
3228                }
3229            }
3230        }
3231        if let Some(validator) = &test.validator {
3232            let entries = serde_json::to_value(validator)?;
3233            for (key, value) in entries.as_object().unwrap() {
3234                if key == "ledger" {
3235                    // Ledger flag is a special case as it is passed separately to the rest of
3236                    // these validator flags.
3237                    continue;
3238                };
3239                if key == "account" {
3240                    for entry in value.as_array().unwrap() {
3241                        // Push the account flag for each array entry
3242                        flags.push("--account".to_string());
3243                        flags.push(entry["address"].as_str().unwrap().to_string());
3244                        flags.push(entry["filename"].as_str().unwrap().to_string());
3245                    }
3246                } else if key == "account_dir" {
3247                    for entry in value.as_array().unwrap() {
3248                        flags.push("--account-dir".to_string());
3249                        flags.push(entry["directory"].as_str().unwrap().to_string());
3250                    }
3251                } else if key == "clone" {
3252                    // Client for fetching accounts data
3253                    let client = if let Some(url) = entries["url"].as_str() {
3254                        create_client(url)
3255                    } else {
3256                        return Err(anyhow!(
3257                            "Validator url for Solana's JSON RPC should be provided in order to clone accounts from it"
3258                        ));
3259                    };
3260
3261                    let pubkeys = value
3262                        .as_array()
3263                        .unwrap()
3264                        .iter()
3265                        .map(|entry| {
3266                            let address = entry["address"].as_str().unwrap();
3267                            Pubkey::from_str(address)
3268                                .map_err(|_| anyhow!("Invalid pubkey {}", address))
3269                        })
3270                        .collect::<Result<HashSet<Pubkey>>>()?
3271                        .into_iter()
3272                        .collect::<Vec<_>>();
3273                    let accounts = client.get_multiple_accounts(&pubkeys)?;
3274
3275                    for (pubkey, account) in pubkeys.into_iter().zip(accounts) {
3276                        match account {
3277                            Some(account) => {
3278                                // Use a different flag for program accounts to fix the problem
3279                                // described in https://github.com/anza-xyz/agave/issues/522
3280                                if account.owner == bpf_loader_upgradeable::id()
3281                                    // Only programs are supported with `--clone-upgradeable-program`
3282                                    && matches!(
3283                                        account.deserialize_data::<UpgradeableLoaderState>()?,
3284                                        UpgradeableLoaderState::Program { .. }
3285                                    )
3286                                {
3287                                    flags.push("--clone-upgradeable-program".to_string());
3288                                    flags.push(pubkey.to_string());
3289                                } else {
3290                                    flags.push("--clone".to_string());
3291                                    flags.push(pubkey.to_string());
3292                                }
3293                            }
3294                            _ => return Err(anyhow!("Account {} not found", pubkey)),
3295                        }
3296                    }
3297                } else if key == "deactivate_feature" {
3298                    // Verify that the feature flags are valid pubkeys
3299                    let pubkeys_result: Result<Vec<Pubkey>, _> = value
3300                        .as_array()
3301                        .unwrap()
3302                        .iter()
3303                        .map(|entry| {
3304                            let feature_flag = entry.as_str().unwrap();
3305                            Pubkey::from_str(feature_flag).map_err(|_| {
3306                                anyhow!("Invalid pubkey (feature flag) {}", feature_flag)
3307                            })
3308                        })
3309                        .collect();
3310                    let features = pubkeys_result?;
3311                    for feature in features {
3312                        flags.push("--deactivate-feature".to_string());
3313                        flags.push(feature.to_string());
3314                    }
3315                } else {
3316                    // Remaining validator flags are non-array types
3317                    flags.push(format!("--{}", key.replace('_', "-")));
3318                    if let serde_json::Value::String(v) = value {
3319                        flags.push(v.to_string());
3320                    } else {
3321                        flags.push(value.to_string());
3322                    }
3323                }
3324            }
3325        }
3326    }
3327
3328    Ok(flags)
3329}
3330
3331fn stream_logs(config: &WithPath<Config>, rpc_url: &str) -> Result<Vec<std::process::Child>> {
3332    let program_logs_dir = Path::new(".anchor").join("program-logs");
3333    if program_logs_dir.exists() {
3334        fs::remove_dir_all(&program_logs_dir)?;
3335    }
3336    fs::create_dir_all(&program_logs_dir)?;
3337
3338    let mut handles = vec![];
3339    for program in config.read_all_programs()? {
3340        let idl_path = Path::new("target")
3341            .join("idl")
3342            .join(&program.lib_name)
3343            .with_extension("json");
3344        let idl = fs::read(idl_path)?;
3345        let idl = convert_idl(&idl)?;
3346
3347        let log_file = File::create(
3348            program_logs_dir.join(format!("{}.{}.log", idl.address, program.lib_name)),
3349        )?;
3350        let stdio = std::process::Stdio::from(log_file);
3351        let child = std::process::Command::new("solana")
3352            .arg("logs")
3353            .arg(idl.address)
3354            .arg("--url")
3355            .arg(rpc_url)
3356            .stdout(stdio)
3357            .spawn()?;
3358        handles.push(child);
3359    }
3360    if let Some(test) = config.test_validator.as_ref() {
3361        if let Some(genesis) = &test.genesis {
3362            for entry in genesis {
3363                let log_file =
3364                    File::create(program_logs_dir.join(&entry.address).with_extension("log"))?;
3365                let stdio = std::process::Stdio::from(log_file);
3366                let child = std::process::Command::new("solana")
3367                    .arg("logs")
3368                    .arg(entry.address.clone())
3369                    .arg("--url")
3370                    .arg(rpc_url)
3371                    .stdout(stdio)
3372                    .spawn()?;
3373                handles.push(child);
3374            }
3375        }
3376    }
3377
3378    Ok(handles)
3379}
3380
3381fn start_test_validator(
3382    cfg: &Config,
3383    test_validator: &Option<TestValidator>,
3384    flags: Option<Vec<String>>,
3385    test_log_stdout: bool,
3386) -> Result<Child> {
3387    let (test_ledger_directory, test_ledger_log_filename) =
3388        test_validator_file_paths(test_validator)?;
3389
3390    // Start a validator for testing.
3391    let (test_validator_stdout, test_validator_stderr) = match test_log_stdout {
3392        true => {
3393            let test_validator_stdout_file = File::create(&test_ledger_log_filename)?;
3394            let test_validator_sterr_file = test_validator_stdout_file.try_clone()?;
3395            (
3396                Stdio::from(test_validator_stdout_file),
3397                Stdio::from(test_validator_sterr_file),
3398            )
3399        }
3400        false => (Stdio::inherit(), Stdio::inherit()),
3401    };
3402
3403    let rpc_url = test_validator_rpc_url(test_validator);
3404
3405    let rpc_port = cfg
3406        .test_validator
3407        .as_ref()
3408        .and_then(|test| test.validator.as_ref().map(|v| v.rpc_port))
3409        .unwrap_or(DEFAULT_RPC_PORT);
3410    if !portpicker::is_free(rpc_port) {
3411        return Err(anyhow!(
3412            "Your configured rpc port: {rpc_port} is already in use"
3413        ));
3414    }
3415    let faucet_port = cfg
3416        .test_validator
3417        .as_ref()
3418        .and_then(|test| test.validator.as_ref().and_then(|v| v.faucet_port))
3419        .unwrap_or(solana_faucet::faucet::FAUCET_PORT);
3420    if !portpicker::is_free(faucet_port) {
3421        return Err(anyhow!(
3422            "Your configured faucet port: {faucet_port} is already in use"
3423        ));
3424    }
3425
3426    let mut validator_handle = std::process::Command::new("solana-test-validator")
3427        .arg("--ledger")
3428        .arg(test_ledger_directory)
3429        .arg("--mint")
3430        .arg(cfg.wallet_kp()?.pubkey().to_string())
3431        .args(flags.unwrap_or_default())
3432        .stdout(test_validator_stdout)
3433        .stderr(test_validator_stderr)
3434        .spawn()
3435        .map_err(|e| anyhow!("Failed to spawn `solana-test-validator`: {e}"))?;
3436
3437    // Wait for the validator to be ready.
3438    let client = create_client(rpc_url);
3439    let mut count = 0;
3440    let ms_wait = test_validator
3441        .as_ref()
3442        .map(|test| test.startup_wait)
3443        .unwrap_or(STARTUP_WAIT);
3444    while count < ms_wait {
3445        let r = client.get_latest_blockhash();
3446        if r.is_ok() {
3447            break;
3448        }
3449        std::thread::sleep(std::time::Duration::from_millis(100));
3450        count += 100;
3451    }
3452    if count >= ms_wait {
3453        eprintln!(
3454            "Unable to get latest blockhash. Test validator does not look started. \
3455            Check {test_ledger_log_filename:?} for errors. Consider increasing [test.startup_wait] in Anchor.toml."
3456        );
3457        validator_handle.kill()?;
3458        std::process::exit(1);
3459    }
3460    Ok(validator_handle)
3461}
3462
3463// Return the URL that solana-test-validator should be running on given the
3464// configuration
3465fn test_validator_rpc_url(test_validator: &Option<TestValidator>) -> String {
3466    match test_validator {
3467        Some(TestValidator {
3468            validator: Some(validator),
3469            ..
3470        }) => format!("http://{}:{}", validator.bind_address, validator.rpc_port),
3471        _ => "http://127.0.0.1:8899".to_string(),
3472    }
3473}
3474
3475// Setup and return paths to the solana-test-validator ledger directory and log
3476// files given the configuration
3477fn test_validator_file_paths(test_validator: &Option<TestValidator>) -> Result<(PathBuf, PathBuf)> {
3478    let ledger_path = match test_validator {
3479        Some(TestValidator {
3480            validator: Some(validator),
3481            ..
3482        }) => PathBuf::from(&validator.ledger),
3483        _ => get_default_ledger_path(),
3484    };
3485
3486    if !ledger_path.is_relative() {
3487        // Prevent absolute paths to avoid someone using / or similar, as the
3488        // directory gets removed
3489        eprintln!("Ledger directory {ledger_path:?} must be relative");
3490        std::process::exit(1);
3491    }
3492    if ledger_path.exists() {
3493        fs::remove_dir_all(&ledger_path)?;
3494    }
3495
3496    fs::create_dir_all(&ledger_path)?;
3497
3498    let log_path = ledger_path.join("test-ledger-log.txt");
3499    Ok((ledger_path, log_path))
3500}
3501
3502fn cluster_url(cfg: &Config, test_validator: &Option<TestValidator>) -> String {
3503    let is_localnet = cfg.provider.cluster == Cluster::Localnet;
3504    match is_localnet {
3505        // Cluster is Localnet, assume the intent is to use the configuration
3506        // for solana-test-validator
3507        true => test_validator_rpc_url(test_validator),
3508        false => cfg.provider.cluster.url().to_string(),
3509    }
3510}
3511
3512fn clean(cfg_override: &ConfigOverride) -> Result<()> {
3513    let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
3514    let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml");
3515    let dot_anchor_dir = cfg_parent.join(".anchor");
3516    let target_dir = cfg_parent.join("target");
3517    let deploy_dir = target_dir.join("deploy");
3518
3519    if dot_anchor_dir.exists() {
3520        fs::remove_dir_all(&dot_anchor_dir)
3521            .map_err(|e| anyhow!("Could not remove directory {:?}: {}", dot_anchor_dir, e))?;
3522    }
3523
3524    if target_dir.exists() {
3525        for entry in fs::read_dir(target_dir)? {
3526            let path = entry?.path();
3527            if path.is_dir() && path != deploy_dir {
3528                fs::remove_dir_all(&path)
3529                    .map_err(|e| anyhow!("Could not remove directory {}: {}", path.display(), e))?;
3530            } else if path.is_file() {
3531                fs::remove_file(&path)
3532                    .map_err(|e| anyhow!("Could not remove file {}: {}", path.display(), e))?;
3533            }
3534        }
3535    } else {
3536        println!("skipping target directory: not found")
3537    }
3538
3539    if deploy_dir.exists() {
3540        for file in fs::read_dir(deploy_dir)? {
3541            let path = file?.path();
3542            if path.extension() != Some(&OsString::from("json")) {
3543                fs::remove_file(&path)
3544                    .map_err(|e| anyhow!("Could not remove file {}: {}", path.display(), e))?;
3545            }
3546        }
3547    } else {
3548        println!("skipping deploy directory: not found")
3549    }
3550
3551    Ok(())
3552}
3553
3554fn deploy(
3555    cfg_override: &ConfigOverride,
3556    program_name: Option<String>,
3557    program_keypair: Option<String>,
3558    verifiable: bool,
3559    no_idl: bool,
3560    solana_args: Vec<String>,
3561) -> Result<()> {
3562    // Execute the code within the workspace
3563    with_workspace(cfg_override, |cfg| {
3564        let url = cluster_url(cfg, &cfg.test_validator);
3565        let keypair = cfg.provider.wallet.to_string();
3566
3567        // Augment the given solana args with recommended defaults.
3568        let client = create_client(&url);
3569        let solana_args = add_recommended_deployment_solana_args(&client, solana_args)?;
3570
3571        // Deploy the programs.
3572        println!("Deploying cluster: {url}");
3573        println!("Upgrade authority: {keypair}");
3574
3575        for mut program in cfg.get_programs(program_name)? {
3576            let binary_path = program.binary_path(verifiable).display().to_string();
3577
3578            println!("Deploying program {:?}...", program.lib_name);
3579            println!("Program path: {binary_path}...");
3580
3581            let (program_keypair_filepath, program_id) = match &program_keypair {
3582                Some(path) => (path.clone(), get_keypair(path)?.pubkey()),
3583                None => (
3584                    program.keypair_file()?.path().display().to_string(),
3585                    program.pubkey()?,
3586                ),
3587            };
3588
3589            // Send deploy transactions using the Solana CLI
3590            let exit = std::process::Command::new("solana")
3591                .arg("program")
3592                .arg("deploy")
3593                .arg("--url")
3594                .arg(&url)
3595                .arg("--keypair")
3596                .arg(&keypair)
3597                .arg("--program-id")
3598                .arg(strip_workspace_prefix(program_keypair_filepath))
3599                .arg(strip_workspace_prefix(binary_path))
3600                .args(&solana_args)
3601                .stdout(Stdio::inherit())
3602                .stderr(Stdio::inherit())
3603                .output()
3604                .expect("Must deploy");
3605
3606            // Check if deployment was successful
3607            if !exit.status.success() {
3608                println!("There was a problem deploying: {exit:?}.");
3609                std::process::exit(exit.status.code().unwrap_or(1));
3610            }
3611
3612            // Get the IDL filepath
3613            let idl_filepath = Path::new("target")
3614                .join("idl")
3615                .join(&program.lib_name)
3616                .with_extension("json");
3617
3618            if let Some(idl) = program.idl.as_mut() {
3619                // Add program address to the IDL.
3620                idl.address = program_id.to_string();
3621
3622                // Persist it.
3623                write_idl(idl, OutFile::File(idl_filepath.clone()))?;
3624
3625                // Upload the IDL to the cluster by default (unless no_idl is set)
3626                if !no_idl {
3627                    // Wait for the program to be confirmed before initializing IDL to prevent
3628                    // race condition where the program isn't yet available in validator cache
3629                    let client = create_client(&url);
3630                    let max_retries = 5;
3631                    let retry_delay = std::time::Duration::from_millis(500);
3632                    let cache_delay = std::time::Duration::from_secs(2);
3633
3634                    println!("Waiting for program {} to be confirmed...", program_id);
3635
3636                    for attempt in 0..max_retries {
3637                        if let Ok(account) = client.get_account(&program_id) {
3638                            if account.executable {
3639                                println!("Program confirmed on-chain");
3640                                std::thread::sleep(cache_delay);
3641                                break;
3642                            }
3643                        }
3644
3645                        if attempt == max_retries - 1 {
3646                            return Err(anyhow!(
3647                                "Timeout waiting for program {} to be confirmed",
3648                                program_id
3649                            ));
3650                        }
3651
3652                        std::thread::sleep(retry_delay);
3653                    }
3654
3655                    // Check if IDL account already exists
3656                    let idl_address = IdlAccount::address(&program_id);
3657                    let idl_account_exists = client.get_account(&idl_address).is_ok();
3658
3659                    if idl_account_exists {
3660                        // IDL account exists, upgrade it
3661                        idl_upgrade(
3662                            cfg_override,
3663                            program_id,
3664                            idl_filepath.display().to_string(),
3665                            None,
3666                        )?;
3667                    } else {
3668                        // IDL account doesn't exist, create it
3669                        idl_init(
3670                            cfg_override,
3671                            program_id,
3672                            idl_filepath.display().to_string(),
3673                            None,
3674                        )?;
3675                    }
3676                }
3677            }
3678        }
3679
3680        println!("Deploy success");
3681
3682        Ok(())
3683    })
3684}
3685
3686fn upgrade(
3687    cfg_override: &ConfigOverride,
3688    program_id: Pubkey,
3689    program_filepath: String,
3690    max_retries: u32,
3691    solana_args: Vec<String>,
3692) -> Result<()> {
3693    let path: PathBuf = program_filepath.parse().unwrap();
3694    let program_filepath = path.canonicalize()?.display().to_string();
3695
3696    with_workspace(cfg_override, |cfg| {
3697        let url = cluster_url(cfg, &cfg.test_validator);
3698        let client = create_client(&url);
3699        let solana_args = add_recommended_deployment_solana_args(&client, solana_args)?;
3700
3701        for retry in 0..(1 + max_retries) {
3702            let exit = std::process::Command::new("solana")
3703                .arg("program")
3704                .arg("deploy")
3705                .arg("--url")
3706                .arg(url.clone())
3707                .arg("--keypair")
3708                .arg(cfg.provider.wallet.to_string())
3709                .arg("--program-id")
3710                .arg(program_id.to_string())
3711                .arg(strip_workspace_prefix(program_filepath.clone()))
3712                .args(&solana_args)
3713                .stdout(Stdio::inherit())
3714                .stderr(Stdio::inherit())
3715                .output()
3716                .expect("Must deploy");
3717            if exit.status.success() {
3718                break;
3719            }
3720
3721            println!("There was a problem deploying: {exit:?}.");
3722            if retry < max_retries {
3723                println!("Retrying {} more time(s)...", max_retries - retry);
3724            } else {
3725                std::process::exit(exit.status.code().unwrap_or(1));
3726            }
3727        }
3728        Ok(())
3729    })
3730}
3731
3732fn create_idl_account(
3733    cfg: &Config,
3734    keypair_path: &str,
3735    program_id: &Pubkey,
3736    idl: &Idl,
3737    priority_fee: Option<u64>,
3738) -> Result<Pubkey> {
3739    // Misc.
3740    let idl_address = IdlAccount::address(program_id);
3741    let keypair = get_keypair(keypair_path)?;
3742    let url = cluster_url(cfg, &cfg.test_validator);
3743    let client = create_client(url);
3744    let idl_data = serialize_idl(idl)?;
3745
3746    // Run `Create instruction.
3747    {
3748        let pda_max_growth = 60_000;
3749        let idl_header_size = 44;
3750        let idl_data_len = idl_data.len() as u64;
3751        // We're only going to support up to 6 instructions in one transaction
3752        // because will anyone really have a >60kb IDL?
3753        if idl_data_len > pda_max_growth {
3754            return Err(anyhow!(
3755                "Your IDL is over 60kb and this isn't supported right now"
3756            ));
3757        }
3758        // Double for future growth.
3759        let data_len = (idl_data_len * 2).min(pda_max_growth - idl_header_size);
3760
3761        let num_additional_instructions = data_len / 10000;
3762        let mut instructions = Vec::new();
3763        let data = serialize_idl_ix(anchor_lang::idl::IdlInstruction::Create { data_len })?;
3764        let program_signer = Pubkey::find_program_address(&[], program_id).0;
3765        let accounts = vec![
3766            AccountMeta::new_readonly(keypair.pubkey(), true),
3767            AccountMeta::new(idl_address, false),
3768            AccountMeta::new_readonly(program_signer, false),
3769            AccountMeta::new_readonly(solana_sdk::system_program::ID, false),
3770            AccountMeta::new_readonly(*program_id, false),
3771        ];
3772        instructions.push(Instruction {
3773            program_id: *program_id,
3774            accounts,
3775            data,
3776        });
3777
3778        for _ in 0..num_additional_instructions {
3779            let data = serialize_idl_ix(anchor_lang::idl::IdlInstruction::Resize { data_len })?;
3780            instructions.push(Instruction {
3781                program_id: *program_id,
3782                accounts: vec![
3783                    AccountMeta::new(idl_address, false),
3784                    AccountMeta::new_readonly(keypair.pubkey(), true),
3785                    AccountMeta::new_readonly(solana_sdk::system_program::ID, false),
3786                ],
3787                data,
3788            });
3789        }
3790        instructions = prepend_compute_unit_ix(instructions, &client, priority_fee)?;
3791
3792        let mut latest_hash = client.get_latest_blockhash()?;
3793        for retries in 0..20 {
3794            if !client.is_blockhash_valid(&latest_hash, client.commitment())? {
3795                latest_hash = client.get_latest_blockhash()?;
3796            }
3797
3798            let tx = Transaction::new_signed_with_payer(
3799                &instructions,
3800                Some(&keypair.pubkey()),
3801                &[&keypair],
3802                latest_hash,
3803            );
3804
3805            match client.send_and_confirm_transaction_with_spinner(&tx) {
3806                Ok(_) => break,
3807                Err(err) => {
3808                    if retries == 19 {
3809                        return Err(anyhow!("Error creating IDL account: {}", err));
3810                    }
3811                    println!("Error creating IDL account: {err}. Retrying...");
3812                }
3813            }
3814        }
3815    }
3816
3817    // Write directly to the IDL account buffer.
3818    idl_write(
3819        cfg,
3820        program_id,
3821        idl,
3822        IdlAccount::address(program_id),
3823        priority_fee,
3824    )?;
3825
3826    Ok(idl_address)
3827}
3828
3829fn create_idl_buffer(
3830    cfg: &Config,
3831    keypair_path: &str,
3832    program_id: &Pubkey,
3833    idl: &Idl,
3834    priority_fee: Option<u64>,
3835) -> Result<Pubkey> {
3836    let keypair = get_keypair(keypair_path)?;
3837    let url = cluster_url(cfg, &cfg.test_validator);
3838    let client = create_client(url);
3839
3840    let buffer = Keypair::new();
3841
3842    // Creates the new buffer account with the system program.
3843    let create_account_ix = {
3844        let space = IdlAccount::DISCRIMINATOR.len() + 32 + 4 + serialize_idl(idl)?.len();
3845        let lamports = client.get_minimum_balance_for_rent_exemption(space)?;
3846        solana_sdk::system_instruction::create_account(
3847            &keypair.pubkey(),
3848            &buffer.pubkey(),
3849            lamports,
3850            space as u64,
3851            program_id,
3852        )
3853    };
3854
3855    // Program instruction to create the buffer.
3856    let create_buffer_ix = {
3857        let accounts = vec![
3858            AccountMeta::new(buffer.pubkey(), false),
3859            AccountMeta::new_readonly(keypair.pubkey(), true),
3860        ];
3861        let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
3862        data.append(&mut IdlInstruction::CreateBuffer.try_to_vec()?);
3863        Instruction {
3864            program_id: *program_id,
3865            accounts,
3866            data,
3867        }
3868    };
3869
3870    let instructions = prepend_compute_unit_ix(
3871        vec![create_account_ix, create_buffer_ix],
3872        &client,
3873        priority_fee,
3874    )?;
3875
3876    let mut latest_hash = client.get_latest_blockhash()?;
3877    for retries in 0..20 {
3878        if !client.is_blockhash_valid(&latest_hash, client.commitment())? {
3879            latest_hash = client.get_latest_blockhash()?;
3880        }
3881        let tx = Transaction::new_signed_with_payer(
3882            &instructions,
3883            Some(&keypair.pubkey()),
3884            &[&keypair, &buffer],
3885            latest_hash,
3886        );
3887        match client.send_and_confirm_transaction_with_spinner(&tx) {
3888            Ok(_) => break,
3889            Err(err) => {
3890                if retries == 19 {
3891                    return Err(anyhow!("Error creating buffer account: {}", err));
3892                }
3893                println!("Error creating buffer account: {err}. Retrying...");
3894            }
3895        }
3896    }
3897
3898    Ok(buffer.pubkey())
3899}
3900
3901// Serialize and compress the idl.
3902fn serialize_idl(idl: &Idl) -> Result<Vec<u8>> {
3903    let json_bytes = serde_json::to_vec(idl)?;
3904    let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
3905    e.write_all(&json_bytes)?;
3906    e.finish().map_err(Into::into)
3907}
3908
3909fn serialize_idl_ix(ix_inner: anchor_lang::idl::IdlInstruction) -> Result<Vec<u8>> {
3910    let mut data = Vec::with_capacity(256);
3911    data.extend_from_slice(&anchor_lang::idl::IDL_IX_TAG.to_le_bytes());
3912    ix_inner.serialize(&mut data)?;
3913    Ok(data)
3914}
3915
3916fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
3917    with_workspace(cfg_override, |cfg| {
3918        println!("Running migration deploy script");
3919
3920        let url = cluster_url(cfg, &cfg.test_validator);
3921        let cur_dir = std::env::current_dir()?;
3922        let migrations_dir = cur_dir.join("migrations");
3923        let deploy_ts = Path::new("deploy.ts");
3924
3925        let use_ts = Path::new("tsconfig.json").exists() && migrations_dir.join(deploy_ts).exists();
3926
3927        if !Path::new(".anchor").exists() {
3928            fs::create_dir(".anchor")?;
3929        }
3930        std::env::set_current_dir(".anchor")?;
3931
3932        let exit = if use_ts {
3933            let module_path = migrations_dir.join(deploy_ts);
3934            let deploy_script_host_str =
3935                rust_template::deploy_ts_script_host(&url, &module_path.display().to_string());
3936            fs::write(deploy_ts, deploy_script_host_str)?;
3937
3938            let pkg_manager_cmd = match &cfg.toolchain.package_manager {
3939                Some(pkg_manager) => pkg_manager.to_string(),
3940                None => PackageManager::default().to_string(),
3941            };
3942
3943            std::process::Command::new(pkg_manager_cmd)
3944                .args([
3945                    "run",
3946                    "ts-node",
3947                    &fs::canonicalize(deploy_ts)?.to_string_lossy(),
3948                ])
3949                .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
3950                .stdout(Stdio::inherit())
3951                .stderr(Stdio::inherit())
3952                .output()?
3953        } else {
3954            let deploy_js = deploy_ts.with_extension("js");
3955            let module_path = migrations_dir.join(&deploy_js);
3956            let deploy_script_host_str =
3957                rust_template::deploy_js_script_host(&url, &module_path.display().to_string());
3958            fs::write(&deploy_js, deploy_script_host_str)?;
3959
3960            std::process::Command::new("node")
3961                .arg(&deploy_js)
3962                .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
3963                .stdout(Stdio::inherit())
3964                .stderr(Stdio::inherit())
3965                .output()?
3966        };
3967
3968        if !exit.status.success() {
3969            eprintln!("Deploy failed.");
3970            std::process::exit(exit.status.code().unwrap());
3971        }
3972
3973        println!("Deploy complete.");
3974        Ok(())
3975    })
3976}
3977
3978fn set_workspace_dir_or_exit() {
3979    let d = match Config::discover(&ConfigOverride::default()) {
3980        Err(err) => {
3981            println!("Workspace configuration error: {err}");
3982            std::process::exit(1);
3983        }
3984        Ok(d) => d,
3985    };
3986    match d {
3987        None => {
3988            println!("Not in anchor workspace.");
3989            std::process::exit(1);
3990        }
3991        Some(cfg) => {
3992            match cfg.path().parent() {
3993                None => {
3994                    println!("Unable to make new program");
3995                }
3996                Some(parent) => {
3997                    if std::env::set_current_dir(parent).is_err() {
3998                        println!("Not in anchor workspace.");
3999                        std::process::exit(1);
4000                    }
4001                }
4002            };
4003        }
4004    }
4005}
4006
4007#[cfg(feature = "dev")]
4008fn airdrop(cfg_override: &ConfigOverride) -> Result<()> {
4009    let url = cfg_override
4010        .cluster
4011        .as_ref()
4012        .unwrap_or(&Cluster::Devnet)
4013        .url();
4014    loop {
4015        let exit = std::process::Command::new("solana")
4016            .arg("airdrop")
4017            .arg("10")
4018            .arg("--url")
4019            .arg(url)
4020            .stdout(Stdio::inherit())
4021            .stderr(Stdio::inherit())
4022            .output()
4023            .expect("Must airdrop");
4024        if !exit.status.success() {
4025            println!("There was a problem airdropping: {:?}.", exit);
4026            std::process::exit(exit.status.code().unwrap_or(1));
4027        }
4028        std::thread::sleep(std::time::Duration::from_millis(10000));
4029    }
4030}
4031
4032fn cluster(_cmd: ClusterCommand) -> Result<()> {
4033    println!("Cluster Endpoints:\n");
4034    println!("* Mainnet - https://api.mainnet-beta.solana.com");
4035    println!("* Devnet  - https://api.devnet.solana.com");
4036    println!("* Testnet - https://api.testnet.solana.com");
4037    Ok(())
4038}
4039
4040fn shell(cfg_override: &ConfigOverride) -> Result<()> {
4041    with_workspace(cfg_override, |cfg| {
4042        let programs = {
4043            // Create idl map from all workspace programs.
4044            let mut idls: HashMap<String, Idl> = cfg
4045                .read_all_programs()?
4046                .iter()
4047                .filter(|program| program.idl.is_some())
4048                .map(|program| {
4049                    (
4050                        program.idl.as_ref().unwrap().metadata.name.clone(),
4051                        program.idl.clone().unwrap(),
4052                    )
4053                })
4054                .collect();
4055            // Insert all manually specified idls into the idl map.
4056            if let Some(programs) = cfg.programs.get(&cfg.provider.cluster) {
4057                let _ = programs
4058                    .iter()
4059                    .map(|(name, pd)| {
4060                        if let Some(idl_fp) = &pd.idl {
4061                            let file_str =
4062                                fs::read_to_string(idl_fp).expect("Unable to read IDL file");
4063                            let idl = serde_json::from_str(&file_str).expect("Idl not readable");
4064                            idls.insert(name.clone(), idl);
4065                        }
4066                    })
4067                    .collect::<Vec<_>>();
4068            }
4069
4070            // Finalize program list with all programs with IDLs.
4071            match cfg.programs.get(&cfg.provider.cluster) {
4072                None => Vec::new(),
4073                Some(programs) => programs
4074                    .iter()
4075                    .filter_map(|(name, program_deployment)| {
4076                        Some(ProgramWorkspace {
4077                            name: name.to_string(),
4078                            program_id: program_deployment.address,
4079                            idl: match idls.get(name) {
4080                                None => return None,
4081                                Some(idl) => idl.clone(),
4082                            },
4083                        })
4084                    })
4085                    .collect::<Vec<ProgramWorkspace>>(),
4086            }
4087        };
4088        let url = cluster_url(cfg, &cfg.test_validator);
4089        let js_code = rust_template::node_shell(&url, &cfg.provider.wallet.to_string(), programs)?;
4090        let mut child = std::process::Command::new("node")
4091            .args(["-e", &js_code, "-i", "--experimental-repl-await"])
4092            .stdout(Stdio::inherit())
4093            .stderr(Stdio::inherit())
4094            .spawn()
4095            .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
4096
4097        if !child.wait()?.success() {
4098            println!("Error running node shell");
4099            return Ok(());
4100        }
4101        Ok(())
4102    })
4103}
4104
4105fn run(cfg_override: &ConfigOverride, script: String, script_args: Vec<String>) -> Result<()> {
4106    with_workspace(cfg_override, |cfg| {
4107        let url = cluster_url(cfg, &cfg.test_validator);
4108        let script = cfg
4109            .scripts
4110            .get(&script)
4111            .ok_or_else(|| anyhow!("Unable to find script"))?;
4112        let script_with_args = format!("{script} {}", script_args.join(" "));
4113        let exit = std::process::Command::new("bash")
4114            .arg("-c")
4115            .arg(&script_with_args)
4116            .env("ANCHOR_PROVIDER_URL", url)
4117            .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
4118            .stdout(Stdio::inherit())
4119            .stderr(Stdio::inherit())
4120            .output()
4121            .unwrap();
4122        if !exit.status.success() {
4123            std::process::exit(exit.status.code().unwrap_or(1));
4124        }
4125        Ok(())
4126    })
4127}
4128
4129fn login(_cfg_override: &ConfigOverride, token: String) -> Result<()> {
4130    let anchor_dir = Path::new(&*shellexpand::tilde("~"))
4131        .join(".config")
4132        .join("anchor");
4133    if !anchor_dir.exists() {
4134        fs::create_dir(&anchor_dir)?;
4135    }
4136
4137    std::env::set_current_dir(&anchor_dir)?;
4138
4139    // Freely overwrite the entire file since it's not used for anything else.
4140    let mut file = File::create("credentials")?;
4141    file.write_all(rust_template::credentials(&token).as_bytes())?;
4142    Ok(())
4143}
4144
4145fn keys(cfg_override: &ConfigOverride, cmd: KeysCommand) -> Result<()> {
4146    match cmd {
4147        KeysCommand::List => keys_list(cfg_override),
4148        KeysCommand::Sync { program_name } => keys_sync(cfg_override, program_name),
4149    }
4150}
4151
4152fn keys_list(cfg_override: &ConfigOverride) -> Result<()> {
4153    with_workspace(cfg_override, |cfg| {
4154        for program in cfg.read_all_programs()? {
4155            let pubkey = program.pubkey()?;
4156            println!("{}: {}", program.lib_name, pubkey);
4157        }
4158        Ok(())
4159    })
4160}
4161
4162/// Sync program `declare_id!` pubkeys with the pubkey from `target/deploy/<KEYPAIR>.json`.
4163fn keys_sync(cfg_override: &ConfigOverride, program_name: Option<String>) -> Result<()> {
4164    with_workspace(cfg_override, |cfg| {
4165        let declare_id_regex = RegexBuilder::new(r#"^(([\w]+::)*)declare_id!\("(\w*)"\)"#)
4166            .multi_line(true)
4167            .build()
4168            .unwrap();
4169
4170        let cfg_cluster = cfg.provider.cluster.to_owned();
4171        println!("Syncing program ids for the configured cluster ({cfg_cluster})\n");
4172
4173        let mut changed_src = false;
4174        for program in cfg.get_programs(program_name)? {
4175            // Get the pubkey from the keypair file
4176            let actual_program_id = program.pubkey()?.to_string();
4177
4178            // Handle declaration in program files
4179            let src_path = program.path.join("src");
4180            let files_to_check = vec![src_path.join("lib.rs"), src_path.join("id.rs")];
4181
4182            for path in files_to_check {
4183                let mut content = match fs::read_to_string(&path) {
4184                    Ok(content) => content,
4185                    Err(_) => continue,
4186                };
4187
4188                let incorrect_program_id = declare_id_regex
4189                    .captures(&content)
4190                    .and_then(|captures| captures.get(3))
4191                    .filter(|program_id_match| program_id_match.as_str() != actual_program_id);
4192                if let Some(program_id_match) = incorrect_program_id {
4193                    println!("Found incorrect program id declaration in {path:?}");
4194
4195                    // Update the program id
4196                    content.replace_range(program_id_match.range(), &actual_program_id);
4197                    fs::write(&path, content)?;
4198
4199                    changed_src = true;
4200                    println!("Updated to {actual_program_id}\n");
4201                    break;
4202                }
4203            }
4204
4205            // Handle declaration in Anchor.toml
4206            'outer: for (cluster, programs) in &mut cfg.programs {
4207                // Only change if the configured cluster matches the program's cluster
4208                if cluster != &cfg_cluster {
4209                    continue;
4210                }
4211
4212                for (name, deployment) in programs {
4213                    // Skip other programs
4214                    if name != &program.lib_name {
4215                        continue;
4216                    }
4217
4218                    if deployment.address.to_string() != actual_program_id {
4219                        println!("Found incorrect program id declaration in Anchor.toml for the program `{name}`");
4220
4221                        // Update the program id
4222                        deployment.address = Pubkey::from_str(&actual_program_id).unwrap();
4223                        fs::write(cfg.path(), cfg.to_string())?;
4224
4225                        println!("Updated to {actual_program_id}\n");
4226                        break 'outer;
4227                    }
4228                }
4229            }
4230        }
4231
4232        println!("All program id declarations are synced.");
4233        if changed_src {
4234            println!("Please rebuild the program to update the generated artifacts.")
4235        }
4236
4237        Ok(())
4238    })
4239}
4240
4241fn localnet(
4242    cfg_override: &ConfigOverride,
4243    skip_build: bool,
4244    skip_deploy: bool,
4245    skip_lint: bool,
4246    env_vars: Vec<String>,
4247    cargo_args: Vec<String>,
4248    arch: ProgramArch,
4249) -> Result<()> {
4250    with_workspace(cfg_override, |cfg| {
4251        // Build if needed.
4252        if !skip_build {
4253            build(
4254                cfg_override,
4255                false,
4256                None,
4257                None,
4258                false,
4259                skip_lint,
4260                None,
4261                None,
4262                None,
4263                BootstrapMode::None,
4264                None,
4265                None,
4266                env_vars,
4267                cargo_args,
4268                false,
4269                arch,
4270            )?;
4271        }
4272
4273        let flags = match skip_deploy {
4274            true => None,
4275            false => Some(validator_flags(cfg, &cfg.test_validator)?),
4276        };
4277
4278        let validator_handle = &mut start_test_validator(cfg, &cfg.test_validator, flags, false)?;
4279
4280        // Setup log reader.
4281        let url = test_validator_rpc_url(&cfg.test_validator);
4282        let log_streams = stream_logs(cfg, &url);
4283
4284        std::io::stdin().lock().lines().next().unwrap().unwrap();
4285
4286        // Check all errors and shut down.
4287        if let Err(err) = validator_handle.kill() {
4288            println!(
4289                "Failed to kill subprocess {}: {}",
4290                validator_handle.id(),
4291                err
4292            );
4293        }
4294
4295        for mut child in log_streams? {
4296            if let Err(err) = child.kill() {
4297                println!("Failed to kill subprocess {}: {}", child.id(), err);
4298            }
4299        }
4300
4301        Ok(())
4302    })
4303}
4304
4305// with_workspace ensures the current working directory is always the top level
4306// workspace directory, i.e., where the `Anchor.toml` file is located, before
4307// and after the closure invocation.
4308//
4309// The closure passed into this function must never change the working directory
4310// to be outside the workspace. Doing so will have undefined behavior.
4311fn with_workspace<R>(
4312    cfg_override: &ConfigOverride,
4313    f: impl FnOnce(&mut WithPath<Config>) -> R,
4314) -> R {
4315    set_workspace_dir_or_exit();
4316
4317    let mut cfg = Config::discover(cfg_override)
4318        .expect("Previously set the workspace dir")
4319        .expect("Anchor.toml must always exist");
4320
4321    let r = f(&mut cfg);
4322
4323    set_workspace_dir_or_exit();
4324
4325    r
4326}
4327
4328fn is_hidden(entry: &walkdir::DirEntry) -> bool {
4329    entry
4330        .file_name()
4331        .to_str()
4332        .map(|s| s == "." || s.starts_with('.') || s == "target")
4333        .unwrap_or(false)
4334}
4335
4336fn get_node_version() -> Result<Version> {
4337    let node_version = std::process::Command::new("node")
4338        .arg("--version")
4339        .stderr(Stdio::inherit())
4340        .output()
4341        .map_err(|e| anyhow::format_err!("node failed: {}", e.to_string()))?;
4342    let output = std::str::from_utf8(&node_version.stdout)?
4343        .strip_prefix('v')
4344        .unwrap()
4345        .trim();
4346    Version::parse(output).map_err(Into::into)
4347}
4348
4349fn add_recommended_deployment_solana_args(
4350    client: &RpcClient,
4351    args: Vec<String>,
4352) -> Result<Vec<String>> {
4353    let mut augmented_args = args.clone();
4354
4355    // If no priority fee is provided, calculate a recommended fee based on recent txs.
4356    if !args.contains(&"--with-compute-unit-price".to_string()) {
4357        let priority_fee = get_recommended_micro_lamport_fee(client)?;
4358        augmented_args.push("--with-compute-unit-price".to_string());
4359        augmented_args.push(priority_fee.to_string());
4360    }
4361
4362    const DEFAULT_MAX_SIGN_ATTEMPTS: u8 = 30;
4363    if !args.contains(&"--max-sign-attempts".to_string()) {
4364        augmented_args.push("--max-sign-attempts".to_string());
4365        augmented_args.push(DEFAULT_MAX_SIGN_ATTEMPTS.to_string());
4366    }
4367
4368    // If no buffer keypair is provided, create a temporary one to reuse across deployments.
4369    // This is particularly useful for upgrading larger programs, which suffer from an increased
4370    // likelihood of some write transactions failing during any single deployment.
4371    if !args.contains(&"--buffer".to_owned()) {
4372        let tmp_keypair_path = std::env::temp_dir().join("anchor-upgrade-buffer.json");
4373        if !tmp_keypair_path.exists() {
4374            if let Err(err) = Keypair::new().write_to_file(&tmp_keypair_path) {
4375                return Err(anyhow!(
4376                    "Error creating keypair for buffer account, {:?}",
4377                    err
4378                ));
4379            }
4380        }
4381
4382        augmented_args.push("--buffer".to_owned());
4383        augmented_args.push(tmp_keypair_path.to_string_lossy().to_string());
4384    }
4385
4386    Ok(augmented_args)
4387}
4388
4389fn get_recommended_micro_lamport_fee(client: &RpcClient) -> Result<u64> {
4390    let mut fees = client.get_recent_prioritization_fees(&[])?;
4391    if fees.is_empty() {
4392        // Fees may be empty, e.g. on localnet
4393        return Ok(0);
4394    }
4395
4396    // Get the median fee from the most recent 150 slots' prioritization fee
4397    fees.sort_unstable_by_key(|fee| fee.prioritization_fee);
4398    let median_index = fees.len() / 2;
4399
4400    let median_priority_fee = if fees.len() % 2 == 0 {
4401        (fees[median_index - 1].prioritization_fee + fees[median_index].prioritization_fee) / 2
4402    } else {
4403        fees[median_index].prioritization_fee
4404    };
4405
4406    Ok(median_priority_fee)
4407}
4408
4409/// Prepend a compute unit ix, if the priority fee is greater than 0.
4410/// This helps to improve the chances that the transaction will land.
4411fn prepend_compute_unit_ix(
4412    instructions: Vec<Instruction>,
4413    client: &RpcClient,
4414    priority_fee: Option<u64>,
4415) -> Result<Vec<Instruction>> {
4416    let priority_fee = match priority_fee {
4417        Some(fee) => fee,
4418        None => get_recommended_micro_lamport_fee(client)?,
4419    };
4420
4421    if priority_fee > 0 {
4422        let mut instructions_appended = instructions.clone();
4423        instructions_appended.insert(
4424            0,
4425            ComputeBudgetInstruction::set_compute_unit_price(priority_fee),
4426        );
4427        Ok(instructions_appended)
4428    } else {
4429        Ok(instructions)
4430    }
4431}
4432
4433fn get_node_dns_option() -> Result<&'static str> {
4434    let version = get_node_version()?;
4435    let req = VersionReq::parse(">=16.4.0").unwrap();
4436    let option = match req.matches(&version) {
4437        true => "--dns-result-order=ipv4first",
4438        false => "",
4439    };
4440    Ok(option)
4441}
4442
4443// Remove the current workspace directory if it prefixes a string.
4444// This is used as a workaround for the Solana CLI using the uriparse crate to
4445// parse args but not handling percent encoding/decoding when using the path as
4446// a local filesystem path. Removing the workspace prefix handles most/all cases
4447// of spaces in keypair/binary paths, but this should be fixed in the Solana CLI
4448// and removed here.
4449fn strip_workspace_prefix(absolute_path: String) -> String {
4450    let workspace_prefix =
4451        std::env::current_dir().unwrap().display().to_string() + std::path::MAIN_SEPARATOR_STR;
4452    absolute_path
4453        .strip_prefix(&workspace_prefix)
4454        .unwrap_or(&absolute_path)
4455        .into()
4456}
4457
4458/// Create a new [`RpcClient`] with `confirmed` commitment level instead of the default(finalized).
4459fn create_client<U: ToString>(url: U) -> RpcClient {
4460    RpcClient::new_with_commitment(url, CommitmentConfig::confirmed())
4461}
4462
4463#[cfg(test)]
4464mod tests {
4465    use super::*;
4466
4467    #[test]
4468    #[should_panic(expected = "Anchor workspace name must be a valid Rust identifier.")]
4469    fn test_init_reserved_word() {
4470        init(
4471            &ConfigOverride {
4472                cluster: None,
4473                wallet: None,
4474            },
4475            "await".to_string(),
4476            true,
4477            true,
4478            PackageManager::default(),
4479            false,
4480            ProgramTemplate::default(),
4481            TestTemplate::default(),
4482            false,
4483        )
4484        .unwrap();
4485    }
4486
4487    #[test]
4488    #[should_panic(expected = "Anchor workspace name must be a valid Rust identifier.")]
4489    fn test_init_reserved_word_from_syn() {
4490        init(
4491            &ConfigOverride {
4492                cluster: None,
4493                wallet: None,
4494            },
4495            "fn".to_string(),
4496            true,
4497            true,
4498            PackageManager::default(),
4499            false,
4500            ProgramTemplate::default(),
4501            TestTemplate::default(),
4502            false,
4503        )
4504        .unwrap();
4505    }
4506
4507    #[test]
4508    #[should_panic(expected = "Anchor workspace name must be a valid Rust identifier.")]
4509    fn test_init_starting_with_digit() {
4510        init(
4511            &ConfigOverride {
4512                cluster: None,
4513                wallet: None,
4514            },
4515            "1project".to_string(),
4516            true,
4517            true,
4518            PackageManager::default(),
4519            false,
4520            ProgramTemplate::default(),
4521            TestTemplate::default(),
4522            false,
4523        )
4524        .unwrap();
4525    }
4526}