solana-verify 0.4.15

A CLI tool for building verifiable Solana programs
mod tests {
    use anyhow::Context;
    use regex::Regex;
    use std::io::Write;
    use std::process::Stdio;

    fn test_verify_program_hash_helper(expected_hash: &str, args: &[&str]) -> anyhow::Result<()> {
        // If the target/debug/solana-verify file does not exist, return a clear error
        let binary_path = "./target/debug/solana-verify";
        if !std::path::Path::new(binary_path).exists() {
            anyhow::bail!(
                "Binary not found at {binary_path}\n\
                 Please build it first by running: cargo build"
            );
        }

        let mut child = std::process::Command::new(binary_path)
            .args(args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .context("Failed to execute solana-verify command")?;

        if let Some(mut stdin) = child.stdin.take() {
            stdin.write_all(b"n")?;
        }

        let output = child
            .wait_with_output()
            .context("Failed to wait for solana-verify command")?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            anyhow::bail!("Command failed: {}", error);
        }

        // Print the last 10 lines of the output
        let output_str = String::from_utf8_lossy(&output.stdout);
        let lines: Vec<&str> = output_str.split('\n').collect();
        let last_10_lines: Vec<String> =
            lines.iter().rev().take(10).map(|s| s.to_string()).collect();
        println!("Last 10 lines of output:\n{}", last_10_lines.join("\n"));

        let re = Regex::new(r"Executable Program Hash from repo: ([a-f0-9]{64})")
            .context("Failed to compile regex")?;

        let program_hash = re
            .captures(&output_str)
            .context("Could not find program hash in output")?
            .get(1)
            .context("Invalid capture group")?
            .as_str();

        assert_eq!(
            program_hash, expected_hash,
            "Program hash {program_hash} does not match expected value {expected_hash}"
        );

        Ok(())
    }

    fn test_local_build_hash_helper(
        build_path: &str,
        executable_path: &str,
        expected_hash: &str,
    ) -> anyhow::Result<()> {
        let build_args = ["build", build_path];
        let child = std::process::Command::new("./target/debug/solana-verify")
            .args(build_args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .context("Failed to execute solana-verify build command")?;

        let output = child
            .wait_with_output()
            .context("Failed to wait for solana-verify build command")?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            anyhow::bail!("Command failed: {}", error);
        }

        let hash_args = ["get-executable-hash", executable_path];
        let child = std::process::Command::new("./target/debug/solana-verify")
            .args(hash_args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .context("Failed to execute solana-verify get-executable-hash command")?;

        let output = child
            .wait_with_output()
            .context("Failed to wait for solana-verify get-executable-hash command")?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr).trim().to_string();
            anyhow::bail!("Command failed: {}", error);
        }

        let hash = String::from_utf8_lossy(&output.stdout).trim().to_string();
        assert_eq!(
            hash, expected_hash,
            "Program hash {} does not match expected value {}",
            hash, expected_hash
        );

        Ok(())
    }

    #[test]
    fn test_phoenix_v1() -> anyhow::Result<()> {
        const EXPECTED_HASH: &str =
            "6877a5b732b3494b828a324ec846d526d962223959534dbaf4209e0da3b2d6a9";
        let args: Vec<&str> =  "verify-from-repo -um --program-id PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY https://github.com/Ellipsis-Labs/phoenix-v1".split(" ").collect();
        test_verify_program_hash_helper(EXPECTED_HASH, &args)?;
        Ok(())
    }

    #[test]
    fn test_squads_v3() -> anyhow::Result<()> {
        const EXPECTED_HASH: &str =
            "72da599d9ee14b2a03a23ccfa6f06d53eea4a00825ad2191929cbd78fb69205c";
        let args: Vec<&str> = "verify-from-repo https://github.com/Squads-Protocol/squads-mpl --commit-hash c95b7673d616c377a349ca424261872dfcf8b19d --program-id SMPLecH534NA9acpos4G6x7uf3LWbCAwZQE9e8ZekMu -um --library-name squads_mpl --bpf".split(" ").collect();
        test_verify_program_hash_helper(EXPECTED_HASH, &args)?;
        Ok(())
    }

    #[test]
    fn test_drift_v2() -> anyhow::Result<()> {
        const EXPECTED_HASH: &str =
            "e31d58edeabc3c30bf6f2aa60bfaa5e492b41ec203e9006404b463e5adee5828";
        let args: Vec<&str> = "verify-from-repo -um --program-id dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH https://github.com/drift-labs/protocol-v2 --commit-hash 110d3ff4f8ba07c178d69f9bfc7b30194fac56d6 --library-name drift".split(" ").collect();
        test_verify_program_hash_helper(EXPECTED_HASH, &args)?;
        Ok(())
    }

    #[test]
    fn test_marginfi_v2() -> anyhow::Result<()> {
        const EXPECTED_HASH: &str =
            "890d68f48f96991016222b1fcbc2cc81b8ef2dcbf280c44fe378c523c108fad5";
        let args: Vec<&str> = "verify-from-repo -um --program-id MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA https://github.com/mrgnlabs/marginfi-v2 --commit-hash d33e649e415c354cc2a1e3c49131725552d69ba0 --library-name marginfi".split(" ").collect();
        test_verify_program_hash_helper(EXPECTED_HASH, &args)?;
        Ok(())
    }

    #[test]
    fn test_games_preset() -> anyhow::Result<()> {
        const EXPECTED_HASH: &str =
            "668ff275819d9276362c6a2636d2a392afe224296e815481b94474785f490025";
        let args: Vec<&str> = "verify-from-repo -um --program-id MkabCfyUD6rBTaYHpgKBBpBo5qzWA2pK2hrGGKMurJt https://github.com/solana-developers/solana-game-preset --commit-hash eaf772fd1f21fe03a9974587f5680635e970be38 --mount-path program".split(" ").collect();
        test_verify_program_hash_helper(EXPECTED_HASH, &args)?;
        Ok(())
    }

    #[test]
    fn test_agave_2_1() -> anyhow::Result<()> {
        const EXPECTED_HASH: &str =
            "29e7713aa3c48e242e2847bc031fe2a03eb61aae5ecaec8728131e16934de465";
        let args: Vec<&str> = "verify-from-repo https://github.com/Woody4618/verify-2-1 --program-id kGYz2q2WUYCXhKpgUF4AMR3seDA9eg8sbirP5dhbyhy --commit-hash e0f138fb58b669791c823f44f878cb3547a92a26".split(" ").collect();
        test_verify_program_hash_helper(EXPECTED_HASH, &args)?;
        Ok(())
    }

    #[test]
    fn test_local_example() -> anyhow::Result<()> {
        const EXPECTED_HASH: &str =
            "08d91368d349c2b56c712422f6d274a1e8f1946ff2ecd1dc3efc3ebace52a760";
        test_local_build_hash_helper(
            "./examples/hello_world",
            "./examples/hello_world/target/deploy/hello_world.so",
            EXPECTED_HASH,
        )
    }

    #[test]
    fn test_local_pinocchio_example() -> anyhow::Result<()> {
        const EXPECTED_HASH: &str =
            "ea119a63af208616d504b0742c01551d3edf56d55554a922448b0d8d0844c374";
        test_local_build_hash_helper(
            "./examples/hello_world_pinocchio",
            "./examples/hello_world_pinocchio/target/deploy/hello_world.so",
            EXPECTED_HASH,
        )
    }

    #[test]
    fn test_verify_from_image() -> anyhow::Result<()> {
        let args: Vec<&str> = "verify-from-image -e examples/hello_world/target/deploy/hello_world.so -i ellipsislabs/hello_world_verifiable_build:latest -p 2ZrriTQSVekoj414Ynysd48jyn4AX6ZF4TTJRqHfbJfn".split(" ").collect();
        let child = std::process::Command::new("./target/debug/solana-verify")
            .args(args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .context("Failed to execute solana-verify command")?;

        let output = child
            .wait_with_output()
            .context("Failed to wait for solana-verify command")?;

        if !output.status.success() {
            let error = String::from_utf8_lossy(&output.stderr);
            anyhow::bail!("Command failed: {}", error);
        }
        Ok(())
    }

    #[test]
    fn test_get_program_hash_legacy_loader() -> anyhow::Result<()> {
        const SPL_TOKEN_PROGRAM_ID: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
        const EXPECTED_HASH: &str =
            "d5a1793250f0f22efc7174bd8399570636e667655179642b2e90b0fb80e09106";
        let args = ["get-program-hash", SPL_TOKEN_PROGRAM_ID];
        let child = std::process::Command::new("./target/debug/solana-verify")
            .args(args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .context("Failed to execute solana-verify command")?;

        let output = child
            .wait_with_output()
            .context("Failed to wait for solana-verify command")?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            anyhow::bail!(
                "get-program-hash for legacy loader program failed: {}",
                stderr
            );
        }

        let stdout = String::from_utf8_lossy(&output.stdout);
        let hash = stdout.trim();
        assert_eq!(
            hash, EXPECTED_HASH,
            "Program hash {} does not match expected value {}",
            hash, EXPECTED_HASH
        );
        Ok(())
    }
}