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>
This commit is contained in:
parent
bf495ac3df
commit
702cbde3e8
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(())
|
||||
}
|
124
avm/src/lib.rs
124
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())?;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
277
cli/src/lib.rs
277
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,
|
||||
)?;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
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"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
println!("{program_id} is verified.");
|
||||
if let Some(commit) = commit_hash {
|
||||
command_args.push("--commit-hash".into());
|
||||
command_args.push(commit);
|
||||
}
|
||||
|
||||
if let Some(name) = program_name {
|
||||
command_args.push("--library-name".into());
|
||||
command_args.push(name);
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 "============================================================"
|
|
@ -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 "============================================================"
|
Loading…
Reference in New Issue