Ver código fonte

feat(cli): Replace `anchor verify` with a robust `solana-verify` wrapper (#3768)

* feat: add support for tuple types in space calculation

This commit introduces the ability to handle tuple types in the space calculation logic. A new `TestTupleStruct` is added to validate various tuple configurations, including nested and option types, ensuring accurate space calculations through comprehensive unit tests.

* clean up unnacessary comments

* chore: fmt fix

* fix: fixing test assert

* fix: fixing test assert

* feat: replace anchor verify to use solana verify under the hood

* feat: Enhance installation process with support for local paths and solana-verify integration

- Made `get_bin_dir_path` public to allow external access.
- Updated `InstallTarget` enum to include a `Path` variant for local repository installations.
- Modified `install_version` function to handle installation from a local path, including reading the version from the Cargo.toml manifest.
- Integrated installation of `solana-verify` during the installation process.
- Updated CLI commands to support the new path option for installation.
- Enhanced verification command to allow for repository URL or current directory options.

* Refactor: Resolve avm binary collision by unifying proxy logic

* feat: Add symlink creation for anchor.exe on Windows

* fix: fmt

* feat: Specify version for solana-verify installation

* feat: Improve anchor command proxying and add testing script

- Updated the main function to enhance the logic for determining if the binary is named `anchor`.
- Introduced a new test script to verify that `anchor` commands correctly proxy to the installed Anchor CLI binary, ensuring expected behavior for both `avm` and `anchor` commands.

* docs: Add caution note regarding mainnet wallet usage in verification test script

* fix: CI fix

* docs: add feature to the changelog

* chore: fix clippy complains (#3776)

* chore: fix clippy complains

* fix lints

* simplify some parts

---------

Co-authored-by: Aursen <aursen@users.noreply.github.com>

* fix: correct median priority fee calculation in get_recommended_micro_lamport_fee function

* fix: clippy

---------

Co-authored-by: Jean (Exotic Markets) <jeanno11@orange.fr>
Co-authored-by: Aursen <aursen@users.noreply.github.com>
Arthur Bretas 2 meses atrás
pai
commit
702cbde3e8

+ 1 - 0
CHANGELOG.md

@@ -13,6 +13,7 @@ The minor version will be incremented upon a breaking change and the patch versi
 ### Features
 
 - lang: Add `#[error]` attribute to `declare_program!` ([#3757](https://github.com/coral-xyz/anchor/pull/3757)).
+- cli: Replace `anchor verify` to use `solana-verify` under the hood, adding automatic installation via AVM, local path support, and future-proof argument passing ([#3768](https://github.com/solana-foundation/anchor/pull/3768)).
 
 ### Fixes
 

+ 0 - 4
avm/Cargo.toml

@@ -7,10 +7,6 @@ edition = "2021"
 name = "avm"
 path = "src/main.rs"
 
-[[bin]]
-name = "anchor"
-path = "src/anchor/main.rs"
-
 [dependencies]
 anyhow = "1.0.32"
 cargo_toml = "0.19.2"

+ 0 - 29
avm/src/anchor/main.rs

@@ -1,29 +0,0 @@
-use std::{env, process::Command};
-
-fn main() -> anyhow::Result<()> {
-    let args = env::args().skip(1).collect::<Vec<String>>();
-
-    let version = avm::current_version()
-        .map_err(|_e| anyhow::anyhow!("Anchor version not set. Please run `avm use latest`."))?;
-
-    let binary_path = avm::version_binary_path(&version);
-    if !binary_path.exists() {
-        anyhow::bail!(
-            "anchor-cli {} not installed. Please run `avm use {}`.",
-            version,
-            version
-        );
-    }
-
-    let exit = Command::new(binary_path)
-        .args(args)
-        .spawn()?
-        .wait_with_output()
-        .expect("Failed to run anchor-cli");
-
-    if !exit.status.success() {
-        std::process::exit(exit.status.code().unwrap_or(1));
-    }
-
-    Ok(())
-}

+ 111 - 13
avm/src/lib.rs

@@ -35,7 +35,7 @@ fn current_version_file_path() -> PathBuf {
 }
 
 /// Path to the current version file $AVM_HOME/bin
-fn get_bin_dir_path() -> PathBuf {
+pub fn get_bin_dir_path() -> PathBuf {
     AVM_HOME.join("bin")
 }
 
@@ -53,7 +53,45 @@ pub fn ensure_paths() {
 
     let bin_dir = get_bin_dir_path();
     if !bin_dir.exists() {
-        fs::create_dir_all(bin_dir).expect("Could not create .avm/bin directory");
+        fs::create_dir_all(&bin_dir).expect("Could not create .avm/bin directory");
+    }
+
+    // Copy the `avm` binary to `~/.avm/bin` so we can create symlinks to it.
+    let avm_in_bin = bin_dir.join("avm");
+    if let Ok(current_avm) = std::env::current_exe() {
+        // Only copy if the paths are different
+        if current_avm != avm_in_bin {
+            if let Err(e) = fs::copy(current_avm, &avm_in_bin) {
+                eprintln!("Failed to copy avm binary: {e}");
+            }
+        }
+    }
+
+    // Create a symlink from `anchor` to `avm` so that the user can run `anchor`
+    // from the command line.
+    #[cfg(unix)]
+    {
+        let anchor_in_bin = bin_dir.join("anchor");
+        if !anchor_in_bin.exists() {
+            if let Err(e) = std::os::unix::fs::symlink(&avm_in_bin, anchor_in_bin) {
+                eprintln!("Failed to create symlink: {e}");
+            }
+        }
+    }
+
+    // On Windows, we create a symlink named `anchor.exe` pointing to the `avm.exe` binary in the bin directory,
+    // so that the user can run `anchor` from the command line.
+    // Note: Creating symlinks on Windows may require administrator privileges or that Developer Mode is enabled.
+
+    #[cfg(windows)]
+    {
+        use std::os::windows::fs::symlink_file;
+        let anchor_in_bin = bin_dir.join("anchor.exe");
+        if !anchor_in_bin.exists() {
+            if let Err(e) = symlink_file(&avm_in_bin, &anchor_in_bin) {
+                eprintln!("Failed to create symlink: {}", e);
+            }
+        }
     }
 
     if !current_version_file_path().exists() {
@@ -102,6 +140,7 @@ pub fn use_version(opt_version: Option<Version>) -> Result<()> {
 pub enum InstallTarget {
     Version(Version),
     Commit(String),
+    Path(PathBuf),
 }
 
 /// Update to the latest version
@@ -170,9 +209,21 @@ pub fn install_version(
     force: bool,
     from_source: bool,
 ) -> Result<()> {
-    let version = match &install_target {
-        InstallTarget::Version(version) => version.to_owned(),
-        InstallTarget::Commit(commit) => get_anchor_version_from_commit(commit)?,
+    let (version, from_source) = match &install_target {
+        InstallTarget::Version(version) => (version.to_owned(), from_source),
+        InstallTarget::Commit(commit) => (get_anchor_version_from_commit(commit)?, true),
+        InstallTarget::Path(path) => {
+            let manifest_path = path.join("cli/Cargo.toml");
+            let manifest = Manifest::from_path(&manifest_path).map_err(|e| {
+                anyhow!(
+                    "Failed to read manifest at {}: {}",
+                    manifest_path.display(),
+                    e
+                )
+            })?;
+            let version = manifest.package().version().parse::<Version>()?;
+            (version, true)
+        }
     };
     // Return early if version is already installed
     if !force && read_installed_versions()?.contains(&version) {
@@ -183,21 +234,44 @@ pub fn install_version(
     let is_commit = matches!(install_target, InstallTarget::Commit(_));
     let is_older_than_v0_31_0 = version < Version::parse("0.31.0")?;
     if from_source || is_commit || is_older_than_v0_31_0 {
-        // Build from source using `cargo install --git`
+        // Build from source using `cargo install`
         let mut args: Vec<String> = vec![
             "install".into(),
             "anchor-cli".into(),
-            "--git".into(),
-            "https://github.com/coral-xyz/anchor".into(),
             "--locked".into(),
             "--root".into(),
             AVM_HOME.to_str().unwrap().into(),
         ];
-        let conditional_args = match install_target {
-            InstallTarget::Version(version) => ["--tag".into(), format!("v{version}")],
-            InstallTarget::Commit(commit) => ["--rev".into(), commit],
-        };
-        args.extend_from_slice(&conditional_args);
+        match install_target {
+            InstallTarget::Version(version) => {
+                args.extend_from_slice(&[
+                    "--git".into(),
+                    "https://github.com/coral-xyz/anchor".into(),
+                    "--tag".into(),
+                    format!("v{version}"),
+                ]);
+            }
+            InstallTarget::Commit(commit) => {
+                args.extend_from_slice(&[
+                    "--git".into(),
+                    "https://github.com/coral-xyz/anchor".into(),
+                    "--rev".into(),
+                    commit,
+                ]);
+            }
+            InstallTarget::Path(path) => {
+                let cli_path = path.join("cli");
+                let path_str = cli_path
+                    .to_str()
+                    .ok_or_else(|| anyhow!("Invalid path string"))?;
+                args.extend_from_slice(&[
+                    "--path".into(),
+                    path_str.to_string(),
+                    "--bin".into(),
+                    "anchor".into(),
+                ]);
+            }
+        }
 
         // If the version is older than v0.31, install using `rustc 1.79.0` to get around the problem
         // explained in https://github.com/coral-xyz/anchor/pull/3143
@@ -280,6 +354,30 @@ pub fn install_version(
         )?;
     }
 
+    println!("Installing solana-verify...");
+    let solana_verify_install_output = Command::new("cargo")
+        .args([
+            "install",
+            "solana-verify",
+            "--git",
+            "https://github.com/Ellipsis-Labs/solana-verifiable-build",
+            "--rev",
+            "568cb334709e88b9b45fc24f1f440eecacf5db54",
+            "--root",
+            AVM_HOME.to_str().unwrap(),
+            "--force",
+            "--locked",
+        ])
+        .stdout(Stdio::inherit())
+        .stderr(Stdio::inherit())
+        .output()
+        .map_err(|e| anyhow!("`cargo install` for `solana-verify` failed: {e}"))?;
+
+    if !solana_verify_install_output.status.success() {
+        return Err(anyhow!("Failed to install `solana-verify`"));
+    }
+    println!("solana-verify successfully installed.");
+
     // If .version file is empty or not parseable, write the newly installed version to it
     if current_version().is_err() {
         let mut current_version_file = fs::File::create(current_version_file_path())?;

+ 74 - 21
avm/src/main.rs

@@ -2,6 +2,7 @@ use anyhow::{anyhow, Error, Result};
 use avm::InstallTarget;
 use clap::{CommandFactory, Parser, Subcommand};
 use semver::Version;
+use std::ffi::OsStr;
 
 #[derive(Parser)]
 #[clap(name = "avm", about = "Anchor version manager", version)]
@@ -19,9 +20,12 @@ pub enum Commands {
     },
     #[clap(about = "Install a version of Anchor", alias = "i")]
     Install {
-        /// Anchor version or commit
-        #[clap(value_parser = parse_install_target)]
-        version_or_commit: InstallTarget,
+        /// Anchor version or commit, conflicts with `--path`
+        #[clap(required_unless_present = "path")]
+        version_or_commit: Option<String>,
+        /// Path to local anchor repo, conflicts with `version_or_commit`
+        #[clap(long, conflicts_with = "version_or_commit")]
+        path: Option<String>,
         #[clap(long)]
         /// Flag to force installation even if the version
         /// is already installed
@@ -51,27 +55,21 @@ fn parse_version(version: &str) -> Result<Version, Error> {
     if version == "latest" {
         avm::get_latest_version()
     } else {
-        Version::parse(version).map_err(|e| anyhow!(e))
+        Ok(Version::parse(version)?)
     }
 }
 
 fn parse_install_target(version_or_commit: &str) -> Result<InstallTarget, Error> {
-    parse_version(version_or_commit)
-        .map(|version| {
-            if version.pre.is_empty() {
-                InstallTarget::Version(version)
-            } else {
-                // Allow `avm install 0.28.0-6cf200493a307c01487c7b492b4893e0d6f6cb23`
-                InstallTarget::Commit(version.pre.to_string())
-            }
-        })
-        .or_else(|version_error| {
-            avm::check_and_get_full_commit(version_or_commit)
-                .map(InstallTarget::Commit)
-                .map_err(|commit_error| {
-                    anyhow!("Not a valid version or commit: {version_error}, {commit_error}")
-                })
-        })
+    if let Ok(version) = parse_version(version_or_commit) {
+        if version.pre.is_empty() {
+            return Ok(InstallTarget::Version(version));
+        }
+        // Allow for e.g. `avm install 0.28.0-6cf200493a307c01487c7b492b4893e0d6f6cb23`
+        return Ok(InstallTarget::Commit(version.pre.to_string()));
+    }
+    avm::check_and_get_full_commit(version_or_commit)
+        .map(InstallTarget::Commit)
+        .map_err(|e| anyhow!("Not a valid version or commit: {e}"))
 }
 
 pub fn entry(opts: Cli) -> Result<()> {
@@ -79,9 +77,17 @@ pub fn entry(opts: Cli) -> Result<()> {
         Commands::Use { version } => avm::use_version(version),
         Commands::Install {
             version_or_commit,
+            path,
             force,
             from_source,
-        } => avm::install_version(version_or_commit, force, from_source),
+        } => {
+            let install_target = if let Some(path) = path {
+                InstallTarget::Path(path.into())
+            } else {
+                parse_install_target(&version_or_commit.unwrap())?
+            };
+            avm::install_version(install_target, force, from_source)
+        }
         Commands::Uninstall { version } => avm::uninstall_version(&version),
         Commands::List {} => avm::list_versions(),
         Commands::Update {} => avm::update(),
@@ -92,7 +98,54 @@ pub fn entry(opts: Cli) -> Result<()> {
     }
 }
 
+fn anchor_proxy() -> Result<()> {
+    let args = std::env::args().skip(1).collect::<Vec<String>>();
+
+    let version = avm::current_version()
+        .map_err(|_e| anyhow::anyhow!("Anchor version not set. Please run `avm use latest`."))?;
+
+    let binary_path = avm::version_binary_path(&version);
+    if !binary_path.exists() {
+        anyhow::bail!(
+            "anchor-cli {} not installed. Please run `avm use {}`.",
+            version,
+            version
+        );
+    }
+
+    let exit = std::process::Command::new(binary_path)
+        .args(args)
+        .env(
+            "PATH",
+            format!(
+                "{}:{}",
+                avm::get_bin_dir_path().to_string_lossy(),
+                std::env::var("PATH").unwrap_or_default()
+            ),
+        )
+        .spawn()?
+        .wait_with_output()
+        .expect("Failed to run anchor-cli");
+
+    if !exit.status.success() {
+        std::process::exit(exit.status.code().unwrap_or(1));
+    }
+
+    Ok(())
+}
+
 fn main() -> Result<()> {
+    // If the binary is named `anchor` then run the proxy.
+    if let Some(stem) = std::env::args()
+        .next()
+        .as_ref()
+        .and_then(|s| std::path::Path::new(s).file_stem().and_then(OsStr::to_str))
+    {
+        if stem == "anchor" {
+            return anchor_proxy();
+        }
+    }
+
     // Make sure the user's home directory is setup with the paths required by AVM.
     avm::ensure_paths();
 

+ 70 - 201
cli/src/lib.rs

@@ -25,9 +25,6 @@ use semver::{Version, VersionReq};
 use serde::Deserialize;
 use serde_json::{json, Map, Value as JsonValue};
 use solana_client::rpc_client::RpcClient;
-use solana_sdk::account_utils::StateMut;
-use solana_sdk::bpf_loader;
-use solana_sdk::bpf_loader_deprecated;
 use solana_sdk::bpf_loader_upgradeable::{self, UpgradeableLoaderState};
 use solana_sdk::commitment_config::CommitmentConfig;
 use solana_sdk::compute_budget::ComputeBudgetInstruction;
@@ -161,34 +158,23 @@ pub enum Command {
     /// Run this command inside a program subdirectory, i.e., in the dir
     /// containing the program's Cargo.toml.
     Verify {
-        /// The deployed program to compare against.
+        /// The program ID to verify.
         program_id: Pubkey,
-        #[clap(short, long)]
+        /// The URL of the repository to verify against. Conflicts with `--current-dir`.
+        #[clap(long, conflicts_with = "current_dir")]
+        repo_url: Option<String>,
+        /// The commit hash to verify against. Requires `--repo-url`.
+        #[clap(long, requires = "repo_url")]
+        commit_hash: Option<String>,
+        /// Verify against the source code in the current directory. Conflicts with `--repo-url`.
+        #[clap(long)]
+        current_dir: bool,
+        /// Name of the program to run the command on. Defaults to the package name.
+        #[clap(long)]
         program_name: Option<String>,
-        /// Version of the Solana toolchain to use. For --verifiable builds
-        /// only.
-        #[clap(short, long)]
-        solana_version: Option<String>,
-        /// Docker image to use. For --verifiable builds only.
-        #[clap(short, long)]
-        docker_image: Option<String>,
-        /// Bootstrap docker image from scratch, installing all requirements for
-        /// verifiable builds. Only works for debian-based images.
-        #[clap(value_enum, short, long, default_value = "none")]
-        bootstrap: BootstrapMode,
-        /// Architecture to use when building the program
-        #[clap(value_enum, long, default_value = "sbf")]
-        arch: ProgramArch,
-        /// Environment variables to pass into the docker container
-        #[clap(short, long, required = false)]
-        env: Vec<String>,
-        /// Arguments to pass to the underlying `cargo build-sbf` command.
-        #[clap(required = false, last = true)]
-        cargo_args: Vec<String>,
-        /// Flag to skip building the program in the workspace,
-        /// use this to save time when running verify and the program code is already built.
-        #[clap(long, required = false)]
-        skip_build: bool,
+        /// Any additional arguments to pass to `solana-verify`.
+        #[clap(raw = true)]
+        args: Vec<String>,
     },
     #[clap(name = "test", alias = "t")]
     /// Runs integration tests.
@@ -820,25 +806,18 @@ fn process_command(opts: Opts) -> Result<()> {
         ),
         Command::Verify {
             program_id,
+            repo_url,
+            commit_hash,
+            current_dir,
             program_name,
-            solana_version,
-            docker_image,
-            bootstrap,
-            env,
-            cargo_args,
-            skip_build,
-            arch,
+            args,
         } => verify(
-            &opts.cfg_override,
             program_id,
+            repo_url,
+            commit_hash,
+            current_dir,
             program_name,
-            solana_version,
-            docker_image,
-            bootstrap,
-            env,
-            cargo_args,
-            skip_build,
-            arch,
+            args,
         ),
         Command::Clean => clean(&opts.cfg_override),
         Command::Deploy {
@@ -2044,81 +2023,62 @@ fn _build_solidity_cwd(
     Ok(())
 }
 
-#[allow(clippy::too_many_arguments)]
-fn verify(
-    cfg_override: &ConfigOverride,
+pub fn verify(
     program_id: Pubkey,
+    repo_url: Option<String>,
+    commit_hash: Option<String>,
+    current_dir: bool,
     program_name: Option<String>,
-    solana_version: Option<String>,
-    docker_image: Option<String>,
-    bootstrap: BootstrapMode,
-    env_vars: Vec<String>,
-    cargo_args: Vec<String>,
-    skip_build: bool,
-    arch: ProgramArch,
+    args: Vec<String>,
 ) -> Result<()> {
-    // Change to the workspace member directory, if needed.
-    if let Some(program_name) = program_name.as_ref() {
-        cd_member(cfg_override, program_name)?;
-    }
+    let mut command_args = Vec::new();
 
-    // Proceed with the command.
-    let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
-    let cargo = Manifest::discover()?.ok_or_else(|| anyhow!("Cargo.toml not found"))?;
-
-    // Build the program we want to verify.
-    let cur_dir = std::env::current_dir()?;
-    if !skip_build {
-        build(
-            cfg_override,
-            true,
-            None,
-            None,
-            true,
-            true,
-            None,
-            solana_version.or_else(|| cfg.toolchain.solana_version.clone()),
-            docker_image,
-            bootstrap,
-            None,
-            None,
-            env_vars,
-            cargo_args.clone(),
-            false,
-            arch,
-        )?;
+    match (current_dir, repo_url) {
+        (true, _) => {
+            let current_path = std::env::current_dir()?
+                .to_str()
+                .ok_or_else(|| anyhow!("Invalid current directory path"))?
+                .to_owned();
+            command_args.push(current_path);
+            command_args.push("--current-dir".into());
+        }
+        (false, Some(url)) => {
+            command_args.push(url);
+        }
+        (false, None) => {
+            return Err(anyhow!(
+                "You must provide either --repo-url or --current-dir"
+            ));
+        }
     }
-    std::env::set_current_dir(cur_dir)?;
 
-    // Verify binary.
-    let binary_name = cargo.lib_name()?;
-    let bin_path = cfg
-        .path()
-        .parent()
-        .ok_or_else(|| anyhow!("Unable to find workspace root"))?
-        .join("target")
-        .join("verifiable")
-        .join(&binary_name)
-        .with_extension("so");
-
-    let url = cluster_url(&cfg, &cfg.test_validator);
-    let bin_ver = verify_bin(program_id, &bin_path, &url)?;
-    if !bin_ver.is_verified {
-        println!("Error: Binaries don't match");
-        std::process::exit(1);
+    if let Some(commit) = commit_hash {
+        command_args.push("--commit-hash".into());
+        command_args.push(commit);
     }
 
-    // Verify IDL (only if it's not a buffer account).
-    let local_idl = generate_idl(&cfg, true, false, &cargo_args)?;
-    if bin_ver.state != BinVerificationState::Buffer {
-        let deployed_idl = fetch_idl(cfg_override, program_id).map(serde_json::from_value)??;
-        if local_idl != deployed_idl {
-            println!("Error: IDLs don't match");
-            std::process::exit(1);
-        }
+    if let Some(name) = program_name {
+        command_args.push("--library-name".into());
+        command_args.push(name);
     }
 
-    println!("{program_id} is verified.");
+    command_args.push("--program-id".into());
+    command_args.push(program_id.to_string());
+
+    command_args.extend(args);
+
+    println!("Verifying program {program_id}");
+    let status = std::process::Command::new("solana-verify")
+        .arg("verify-from-repo")
+        .args(&command_args)
+        .stdout(std::process::Stdio::inherit())
+        .stderr(std::process::Stdio::inherit())
+        .status()
+        .with_context(|| "Failed to run `solana-verify`")?;
+
+    if !status.success() {
+        return Err(anyhow!("Failed to verify program"));
+    }
 
     Ok(())
 }
@@ -2155,97 +2115,6 @@ fn cd_member(cfg_override: &ConfigOverride, program_name: &str) -> Result<()> {
     Err(anyhow!("{} is not part of the workspace", program_name,))
 }
 
-pub fn verify_bin(program_id: Pubkey, bin_path: &Path, cluster: &str) -> Result<BinVerification> {
-    // Use `finalized` state for verify
-    let client = RpcClient::new_with_commitment(cluster, CommitmentConfig::finalized());
-
-    // Get the deployed build artifacts.
-    let (deployed_bin, state) = {
-        let account = client.get_account(&program_id)?;
-        if account.owner == bpf_loader::id() || account.owner == bpf_loader_deprecated::id() {
-            let bin = account.data.to_vec();
-            let state = BinVerificationState::ProgramData {
-                slot: 0, // Need to look through the transaction history.
-                upgrade_authority_address: None,
-            };
-            (bin, state)
-        } else if account.owner == bpf_loader_upgradeable::id() {
-            match account.state()? {
-                UpgradeableLoaderState::Program {
-                    programdata_address,
-                } => {
-                    let account = client.get_account(&programdata_address)?;
-                    let bin = account.data
-                        [UpgradeableLoaderState::size_of_programdata_metadata()..]
-                        .to_vec();
-
-                    if let UpgradeableLoaderState::ProgramData {
-                        slot,
-                        upgrade_authority_address,
-                    } = account.state()?
-                    {
-                        let state = BinVerificationState::ProgramData {
-                            slot,
-                            upgrade_authority_address,
-                        };
-                        (bin, state)
-                    } else {
-                        return Err(anyhow!("Expected program data"));
-                    }
-                }
-                UpgradeableLoaderState::Buffer { .. } => {
-                    let offset = UpgradeableLoaderState::size_of_buffer_metadata();
-                    (
-                        account.data[offset..].to_vec(),
-                        BinVerificationState::Buffer,
-                    )
-                }
-                _ => {
-                    return Err(anyhow!(
-                        "Invalid program id, not a buffer or program account"
-                    ))
-                }
-            }
-        } else {
-            return Err(anyhow!(
-                "Invalid program id, not owned by any loader program"
-            ));
-        }
-    };
-    let mut local_bin = {
-        let mut f = File::open(bin_path)?;
-        let mut contents = vec![];
-        f.read_to_end(&mut contents)?;
-        contents
-    };
-
-    // The deployed program probably has zero bytes appended. The default is
-    // 2x the binary size in case of an upgrade.
-    if local_bin.len() < deployed_bin.len() {
-        local_bin.append(&mut vec![0; deployed_bin.len() - local_bin.len()]);
-    }
-
-    // Finally, check the bytes.
-    let is_verified = local_bin == deployed_bin;
-
-    Ok(BinVerification { state, is_verified })
-}
-
-#[derive(PartialEq, Eq)]
-pub struct BinVerification {
-    pub state: BinVerificationState,
-    pub is_verified: bool,
-}
-
-#[derive(PartialEq, Eq)]
-pub enum BinVerificationState {
-    Buffer,
-    ProgramData {
-        slot: u64,
-        upgrade_authority_address: Option<Pubkey>,
-    },
-}
-
 fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
     match subcmd {
         IdlCommand::Init {
@@ -4822,7 +4691,7 @@ fn get_recommended_micro_lamport_fee(client: &RpcClient) -> Result<u64> {
     fees.sort_unstable_by_key(|fee| fee.prioritization_fee);
     let median_index = fees.len() / 2;
 
-    let median_priority_fee = if fees.len().is_multiple_of(2) {
+    let median_priority_fee = if fees.len() % 2 == 0 {
         (fees[median_index - 1].prioritization_fee + fees[median_index].prioritization_fee) / 2
     } else {
         fees[median_index].prioritization_fee

+ 6 - 3
lang/syn/src/codegen/accounts/constraints.rs

@@ -360,12 +360,15 @@ pub fn generate_constraint_raw(ident: &Ident, c: &ConstraintRaw) -> proc_macro2:
 
 pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
     let ident = &f.ident;
-    let maybe_deref = match &f.ty {
+    let maybe_deref = if match &f.ty {
         Ty::Account(AccountTy { boxed, .. })
         | Ty::InterfaceAccount(InterfaceAccountTy { boxed, .. }) => *boxed,
         _ => false,
-    }
-    .then_some(quote!(*));
+    } {
+        quote!(*)
+    } else {
+        Default::default()
+    };
     let owner_address = &c.owner_address;
     let error = generate_custom_error(
         ident,

+ 72 - 0
tests/anchor-cli-idl/anchor-symlink-test.sh

@@ -0,0 +1,72 @@
+#!/bin/bash
+
+# This test script verifies that running 'anchor' commands
+# correctly proxies to the installed Anchor CLI binary (e.g., shows Anchor "something")
+# instead of launching AVM itself.
+
+# Exit on first error
+set -e
+
+# --- Helper for timing and logging ---
+function step_start {
+    echo ""
+    echo "============================================================"
+    echo "🚀 Starting Step: $1"
+    echo "============================================================"
+    STEP_START_TIME=$(date +%s)
+}
+
+function step_end {
+    local end_time=$(date +%s)
+    local duration=$((end_time - STEP_START_TIME))
+    echo "✅ Step completed in ${duration}s."
+}
+
+
+trap 'echo ""; echo "🧹 Cleaning up..."' INT TERM EXIT
+
+step_start "Building local avm"
+(cd ../.. && cargo build --package avm)
+step_end
+
+step_start "Installing local anchor-cli via avm (force to ensure fresh install)"
+../../target/debug/avm install --path ../.. --force
+step_end
+
+# --- Set a Specific Version ---
+step_start "Setting AVM to use a known version (e.g., latest)"
+../../target/debug/avm use latest
+step_end
+
+# --- Test 'avm' Command (Should Show AVM Help) ---
+step_start "Testing 'avm --help' (should show AVM usage)"
+AVM_OUTPUT=$(~/.avm/bin/avm --help 2>&1) || true
+if echo "$AVM_OUTPUT" | grep -q "Anchor version manager"; then
+  echo "✅ 'avm --help' shows AVM usage as expected."
+else
+  echo "❌ Test failed: 'avm --help' did not show expected AVM output."
+  echo "$AVM_OUTPUT"
+  exit 1
+fi
+step_end
+
+# --- Test 'anchor' Command (Should Proxy to Anchor CLI) ---
+step_start "Testing 'anchor --version' (should show Anchor CLI version, not AVM)"
+ANCHOR_OUTPUT=$(~/.avm/bin/anchor --version 2>&1) || true
+if echo "$ANCHOR_OUTPUT" | grep -q "anchor-cli"; then
+  echo "✅ 'anchor --version' proxies to Anchor CLI successfully."
+elif echo "$ANCHOR_OUTPUT" | grep -q "Anchor version manager"; then
+  echo "❌ Test failed: 'anchor --version' is still launching AVM instead of proxying."
+  echo "$ANCHOR_OUTPUT"
+  exit 1
+else
+  echo "❌ Test failed: Unexpected output from 'anchor --version'."
+  echo "$ANCHOR_OUTPUT"
+  exit 1
+fi
+step_end
+
+echo ""
+echo "============================================================"
+echo "🎉 All tests passed successfully! 🎉"
+echo "============================================================"

+ 90 - 0
tests/anchor-cli-idl/verify_test.sh

@@ -0,0 +1,90 @@
+#!/bin/bash
+
+# This test script verifies that `avm` correctly installs `solana-verify`
+# and that `anchor verify` can successfully verify a known public program.
+
+# Exit on first error
+set -e
+
+# --- Helper for timing and logging ---
+function step_start {
+    echo ""
+    echo "============================================================"
+    echo "🚀 Starting Step: $1"
+    echo "============================================================"
+    STEP_START_TIME=$(date +%s)
+}
+
+function step_end {
+    local end_time=$(date +%s)
+    local duration=$((end_time - STEP_START_TIME))
+    echo "✅ Step completed in ${duration}s."
+}
+
+# --- Cleanup ---
+trap 'echo ""; echo "🧹 Cleaning up..."' INT TERM EXIT
+
+# --- Dependency Checks ---
+step_start "Checking dependencies"
+# Check for docker
+if ! command -v docker &> /dev/null; then
+  echo "❌ Docker is not installed. Please install it to run this test."
+  exit 1
+fi
+if ! docker info > /dev/null 2>&1; then
+  echo "❌ Docker is not running. Please start Docker and run the test again."
+  exit 1
+fi
+step_end
+
+# --- Build and Install Anchor from Local Source ---
+step_start "Building local avm"
+(cd ../.. && cargo build --package avm)
+step_end
+
+step_start "Installing local anchor-cli and solana-verify via avm"
+../../target/debug/avm install --path ../.. --force
+step_end
+
+# --- Verify `solana-verify` Installation ---
+step_start "Checking for solana-verify installation"
+if ! [ -x ~/.avm/bin/solana-verify ]; then
+    echo "❌ solana-verify was not found in ~/.avm/bin."
+    exit 1
+fi
+echo "-> solana-verify found successfully."
+step_end
+
+# --- Verify a Known Public Program ---
+step_start "Verifying a known public program (Phoenix on Mainnet)"
+OUTPUT=$(../../target/debug/anchor verify PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY \
+    --repo-url https://github.com/Ellipsis-Labs/phoenix-v1 \
+    -- \
+    --skip-prompt \
+    --url https://api.mainnet-beta.solana.com 2>&1) || true
+step_end
+
+
+# --- Check Results ---
+step_start "Checking verification results"
+if echo "$OUTPUT" | grep -q "Program hash matches ✅"; then
+  # This is the expected outcome in a test environment without a funded mainnet wallet.
+  # ⚠️ CAUTION: if you have a mainnet wallet with funds, and configured the CLI to use it, this will fail. And you will lose money.
+  if echo "$OUTPUT" | grep -q "Failed to send verification transaction to the blockchain"; then
+    echo "✅ Test successful: Verification passed and transaction failed as expected."
+  else
+    echo "❌ Test failed: Verification passed, but an unexpected error occurred."
+    echo "$OUTPUT"
+    exit 1
+  fi
+else
+  echo "❌ Test failed: Verification did not pass."
+  echo "$OUTPUT"
+  exit 1
+fi
+step_end
+
+echo ""
+echo "============================================================"
+echo "🎉 All tests passed successfully! 🎉"
+echo "============================================================"