export-sui-verifier 0.2.2

CLI for exporting Groth16 artifacts to Sui Move verifier packages.
use assert_cmd::Command;
use std::fs;
use std::path::PathBuf;

fn repo_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("..")
        .join("..")
}

fn temp_output_dir(name: &str) -> PathBuf {
    let mut dir = std::env::temp_dir();
    dir.push(format!(
        "export_sui_verifier_cli_{name}_{}",
        std::process::id()
    ));
    if dir.exists() {
        let _ = fs::remove_dir_all(&dir);
    }
    dir
}

#[test]
fn help_omits_format_curve_and_output_flags() {
    let assert = Command::cargo_bin("export-sui-verifier")
        .unwrap()
        .args(["--help"])
        .assert()
        .success()
        .stdout(predicates::str::contains("--out"));

    let stdout = String::from_utf8_lossy(&assert.get_output().stdout);
    assert!(!stdout.contains("--format"));
    assert!(!stdout.contains("--curve"));
    assert!(!stdout.contains("--output"));
}

#[test]
fn snarkjs_vk_only_command_infers_curve_from_metadata() {
    let artifact_dir = repo_root()
        .join("examples")
        .join("ark-mimc")
        .join("artifacts")
        .join("bn254");
    let output = temp_output_dir("snarkjs_auto_curve_vk_only").join("generated");

    Command::cargo_bin("export-sui-verifier")
        .unwrap()
        .args(["--vk"])
        .arg(artifact_dir.join("verification_key.json"))
        .args(["--out"])
        .arg(&output)
        .args([
            "--package-name",
            "groth16_bn254_snarkjs_auto_curve",
            "--module-name",
            "verifier",
        ])
        .assert()
        .success();

    assert!(output.join("Move.toml").exists());
    assert!(output.join("sources").join("verifier.move").exists());
    assert!(!output.join("tests").exists());
}

#[test]
fn generation_uses_simple_defaults_when_names_are_omitted() {
    let artifact_dir = repo_root()
        .join("examples")
        .join("ark-mimc")
        .join("artifacts")
        .join("bn254");
    let output = temp_output_dir("default_names").join("my-verifier");

    Command::cargo_bin("export-sui-verifier")
        .unwrap()
        .args(["--vk"])
        .arg(artifact_dir.join("verification_key.json"))
        .args(["--out"])
        .arg(&output)
        .args(["--force"])
        .assert()
        .success();

    let move_toml = fs::read_to_string(output.join("Move.toml")).unwrap();
    let verifier = fs::read_to_string(output.join("sources").join("verifier.move")).unwrap();
    assert!(move_toml.contains("name = \"my_verifier\""));
    assert!(verifier.contains("module my_verifier::verifier"));
}

#[test]
fn generation_accepts_test_mode_from_cli() {
    let artifact_dir = repo_root()
        .join("examples")
        .join("ark-mimc")
        .join("artifacts")
        .join("bn254");
    let output = temp_output_dir("test_mode").join("generated");

    Command::cargo_bin("export-sui-verifier")
        .unwrap()
        .args(["--vk"])
        .arg(artifact_dir.join("verification_key.json"))
        .args(["--out"])
        .arg(&output)
        .args(["--mode", "test"])
        .assert()
        .success();

    let verifier = fs::read_to_string(output.join("sources").join("verifier.move")).unwrap();
    assert!(verifier.contains("entry fun verify_entry"));
}

#[test]
fn groth16_subcommand_is_no_longer_supported() {
    Command::cargo_bin("export-sui-verifier")
        .unwrap()
        .args(["groth16", "--help"])
        .assert()
        .failure();
}

#[test]
fn format_curve_and_output_flags_are_rejected() {
    for flag in ["--format", "--curve", "--output"] {
        Command::cargo_bin("export-sui-verifier")
            .unwrap()
            .args([flag, "auto"])
            .assert()
            .failure();
    }
}

#[test]
fn proof_data_infers_curve_from_metadata() {
    let artifact_dir = repo_root()
        .join("examples")
        .join("ark-mimc")
        .join("artifacts")
        .join("bn254");

    Command::cargo_bin("export-sui-verifier")
        .unwrap()
        .args(["proof-data", "--vk"])
        .arg(artifact_dir.join("verification_key.json"))
        .args(["--proof"])
        .arg(artifact_dir.join("proof.json"))
        .assert()
        .success()
        .stdout(predicates::str::contains("fun proof_bytes(): vector<u8>"));
}

#[test]
fn arkworks_vk_only_command_generates_package() {
    let bundle = repo_root()
        .join("examples")
        .join("ark-mimc")
        .join("artifacts")
        .join("bn254")
        .join("groth16_artifacts.json");
    let bundle_json = fs::read_to_string(&bundle).unwrap();
    let bundle_value: serde_json::Value = serde_json::from_str(&bundle_json).unwrap();
    let vk_only = serde_json::json!({
        "curve": "bn254",
        "verification_key": bundle_value.get("vk").unwrap(),
    });

    let temp = temp_output_dir("arkworks_vk_only");
    fs::create_dir_all(&temp).unwrap();
    let vk_path = temp.join("arkworks_verification_key.json");
    let output = temp.join("generated");
    fs::write(&vk_path, serde_json::to_string_pretty(&vk_only).unwrap()).unwrap();

    Command::cargo_bin("export-sui-verifier")
        .unwrap()
        .args(["--vk"])
        .arg(&vk_path)
        .args(["--out"])
        .arg(&output)
        .args([
            "--package-name",
            "groth16_bn254_arkworks_verifier",
            "--module-name",
            "verifier",
        ])
        .assert()
        .success();

    assert!(output.join("Move.toml").exists());
    assert!(output.join("sources").join("verifier.move").exists());
    assert!(!output.join("tests").exists());
}

#[test]
fn snarkjs_command_can_run_sui_test() {
    let artifact_dir = repo_root()
        .join("examples")
        .join("MulCircuit")
        .join("artifacts")
        .join("bls12_381");
    let output = temp_output_dir("snarkjs_run_sui_test").join("generated");

    Command::cargo_bin("export-sui-verifier")
        .unwrap()
        .args(["--vk"])
        .arg(artifact_dir.join("verification_key.json"))
        .args(["--proof"])
        .arg(artifact_dir.join("proof.json"))
        .args(["--public"])
        .arg(artifact_dir.join("public.json"))
        .args(["--out"])
        .arg(&output)
        .args([
            "--package-name",
            "mul_circuit_bls12381_verifier",
            "--module-name",
            "verifier",
            "--run-sui-test",
        ])
        .assert()
        .success();

    assert!(output.join("Move.toml").exists());
    assert!(output.join("tests").join("verifier_tests.move").exists());
}

#[test]
fn snarkjs_vk_only_command_generates_package_without_tests() {
    let artifact_dir = repo_root()
        .join("examples")
        .join("ark-mimc")
        .join("artifacts")
        .join("bn254");
    let output = temp_output_dir("snarkjs_vk_only").join("generated");

    Command::cargo_bin("export-sui-verifier")
        .unwrap()
        .args(["--vk"])
        .arg(artifact_dir.join("verification_key.json"))
        .args(["--out"])
        .arg(&output)
        .args([
            "--package-name",
            "groth16_bn254_snarkjs_vk_only",
            "--module-name",
            "verifier",
        ])
        .assert()
        .success();

    assert!(output.join("Move.toml").exists());
    assert!(output.join("sources").join("verifier.move").exists());
    assert!(!output.join("tests").exists());
}

#[test]
fn proof_data_prints_snippets_matching_generated_sui_tests() {
    let artifact_dir = repo_root()
        .join("examples")
        .join("ark-mimc")
        .join("artifacts")
        .join("bn254");
    let output = temp_output_dir("proof_data_matches_tests").join("generated");

    Command::cargo_bin("export-sui-verifier")
        .unwrap()
        .args(["--vk"])
        .arg(artifact_dir.join("verification_key.json"))
        .args(["--proof"])
        .arg(artifact_dir.join("proof.json"))
        .args(["--out"])
        .arg(&output)
        .args([
            "--package-name",
            "groth16_bn254_snarkjs_proof_data",
            "--module-name",
            "verifier",
            "--force",
        ])
        .assert()
        .success();

    let generated_tests =
        fs::read_to_string(output.join("tests").join("verifier_tests.move")).unwrap();

    let assert = Command::cargo_bin("export-sui-verifier")
        .unwrap()
        .args(["proof-data", "--vk"])
        .arg(artifact_dir.join("verification_key.json"))
        .args(["--proof"])
        .arg(artifact_dir.join("proof.json"))
        .assert()
        .success();

    let stdout = String::from_utf8_lossy(&assert.get_output().stdout);
    assert!(stdout.contains("fun proof_bytes(): vector<u8>"));
    assert!(stdout.contains("fun public_inputs_bytes(): vector<u8>"));
    assert!(generated_tests.contains(stdout.trim()));
}