anchor_cli/
lib.rs

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