warriors/trisha/rs/tests/triton_compile.rs

// ---
// tags: trident, rust
// crystal-type: source
// crystal-domain: comp
// ---
//! Moved from trident's api/tests/ โ€” the core no longer lowers to Triton
//! assembly in-process (.claude/plans/warrior-owns-lowering.md S3 in
//! trident); this is now the warrior's own correctness suite.

use std::path::Path;

#[allow(dead_code)]
#[path = "support/plumb_v2.rs"]
mod plumb_vectors;

fn assert_plumb_v2_path_authentication(tasm: &str, case: plumb_vectors::Case) {
    use triton_vm::prelude::*;
    assert!(tasm.contains("os_neptune_standards_plumb__update_leaf:"));
    assert!(
        tasm.contains("assert_vector"),
        "missing full digest assertion"
    );
    let program = Program::from_code(tasm).unwrap();
    let run = |public: &[u64], secret: &[u64]| {
        VM::run(
            program.clone(),
            PublicInput::new(
                public
                    .iter()
                    .copied()
                    .map(BFieldElement::new)
                    .collect::<Vec<_>>(),
            ),
            NonDeterminism::new(
                secret
                    .iter()
                    .copied()
                    .map(BFieldElement::new)
                    .collect::<Vec<_>>(),
            ),
        )
    };
    let output =
        run(&case.public, &case.secret).expect("independent same-path transition must execute");
    assert_eq!(
        output.iter().map(|x| x.value()).collect::<Vec<_>>(),
        case.output
    );
    for root_start in [1, 6] {
        for coordinate in 0..5 {
            let mut public = case.public.clone();
            public[root_start + coordinate] ^= 1;
            assert!(
                run(&public, &case.secret).is_err(),
                "unauthenticated root coordinate accepted"
            );
        }
    }
    let mut secret = case.secret.clone();
    *secret.last_mut().unwrap() ^= 1;
    assert!(
        run(&case.public, &secret).is_err(),
        "changed shared path accepted"
    );
    secret.pop();
    assert!(
        run(&case.public, &secret).is_err(),
        "truncated shared path accepted"
    );
}

/// Compile a Triton source string, temp-file bridged (`build_tasm` takes a
/// path, not a source string).
#[allow(dead_code)]
fn compile_triton(source: &str, filename: &str) -> Result<String, String> {
    // A unique dir per call โ€” parallel #[test] threads must not share a
    // path (build_tasm needs a real file: full module resolution runs
    // from it, unlike the in-memory compile this bridges).
    let dir = tempfile::tempdir().map_err(|e| e.to_string())?;
    let path = dir.path().join(filename);
    std::fs::write(&path, source).map_err(|e| e.to_string())?;
    trisha_rs::build_tasm(
        &path,
        if source.contains("os.neptune") {
            "neptune"
        } else {
            "triton"
        },
        "debug",
    )
}

/// Like `compile_triton`, at a named profile (debug/release).
#[allow(dead_code)]
fn compile_triton_profile(source: &str, filename: &str, profile: &str) -> Result<String, String> {
    let dir = tempfile::tempdir().map_err(|e| e.to_string())?;
    let path = dir.path().join(filename);
    std::fs::write(&path, source).map_err(|e| e.to_string())?;
    trisha_rs::build_tasm(&path, "triton", profile)
}

/// Compile a project entry point (a real .tri file, resolved against the
/// trident repo checked out beside this one).
#[allow(dead_code)]
fn compile_project_triton(path: &Path) -> Result<String, String> {
    trisha_rs::build_tasm(
        path,
        if path.components().any(|part| part.as_os_str() == "neptune") {
            "neptune"
        } else {
            "triton"
        },
        "debug",
    )
}

/// A stdlib/vm/os path is relative to the trident repo root; trisha's own
/// tests run from trisha's workspace root, so re-root them at `../trident`
/// (the sibling-repo layout every companion-repo doc in this stack assumes).
#[allow(dead_code)]
fn trident_repo_path(rel: &str) -> std::path::PathBuf {
    if let Some(name) = rel.strip_prefix("os/neptune/") {
        if name.starts_with("locks/")
            || name.starts_with("types/")
            || matches!(name, "standards/coin.tri" | "standards/card.tri")
        {
            return Path::new(env!("CARGO_MANIFEST_DIR"))
                .join("../examples/neptune")
                .join(name);
        }
    }
    if let Some(name) = rel.strip_prefix("os/neptune/programs/") {
        return Path::new(env!("CARGO_MANIFEST_DIR"))
            .join("../examples/experimental/neptune")
            .join(name);
    }
    if rel.starts_with("os/neptune/") || rel.starts_with("vm/triton/") {
        return Path::new(env!("CARGO_MANIFEST_DIR"))
            .join("../lib")
            .join(rel);
    }
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../../trident/lib")
        .join(rel)
}

#[test]
fn test_compile_valid_program() {
    let source =
        "program test\nfn main() {\n    let x: Field = pub_read()\n    pub_write(x + 1)\n}";
    let result = compile_triton(source, "test.tri");
    assert!(result.is_ok());
    let tasm = result.unwrap();
    assert!(tasm.contains("read_io 1"));
    assert!(tasm.contains("write_io 1"));
}

#[test]
fn test_compile_type_error_returns_err() {
    let source = "program test\nfn main() {\n    let x: U32 = pub_read()\n}";
    let result = compile_triton(source, "test.tri");
    assert!(result.is_err());
}

#[test]
fn test_deeply_nested_if() {
    let source = r#"program test
fn main() {
let x: Field = pub_read()
if x == 0 {
    if x == 1 {
        if x == 2 {
            if x == 3 {
                if x == 4 {
                    pub_write(x)
                }
            }
        }
    }
}
}
"#;
    assert!(compile_triton(source, "test.tri").is_ok());
}

#[test]
fn test_deeply_nested_for() {
    let source = r#"program test
fn main() {
let mut s: Field = 0
for i in 0..3 bounded 3 {
    for j in 0..3 bounded 3 {
        for k in 0..3 bounded 3 {
            s = s + 1
        }
    }
}
pub_write(s)
}
"#;
    let result = compile_triton(source, "test.tri");
    assert!(result.is_ok());
    let tasm = result.unwrap();
    assert!(tasm.contains("write_io 1"));
}

#[test]
fn test_many_variables_spill() {
    // Force stack spilling by having many live variables
    let source = r#"program test
fn main() {
let a: Field = pub_read()
let b: Field = pub_read()
let c: Field = pub_read()
let d: Field = pub_read()
let e: Field = pub_read()
let f: Field = pub_read()
let g: Field = pub_read()
let h: Field = pub_read()
let i: Field = pub_read()
let j: Field = pub_read()
let k: Field = pub_read()
let l: Field = pub_read()
let m: Field = pub_read()
let n: Field = pub_read()
let o: Field = pub_read()
let p: Field = pub_read()
let q: Field = pub_read()
let r: Field = pub_read()
pub_write(a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r)
}
"#;
    let result = compile_triton(source, "test.tri");
    assert!(
        result.is_ok(),
        "should handle 18 live variables with spilling"
    );
    let tasm = result.unwrap();
    // All 18 variables contribute to the output โ€” verify the sum reaches write_io
    assert!(
        tasm.contains("write_io 1"),
        "18-variable sum should produce write_io"
    );
}

#[test]
fn test_chain_of_function_calls() {
    let source = r#"program test
fn add1(x: Field) -> Field {
x + 1
}

fn add2(x: Field) -> Field {
add1(add1(x))
}

fn add4(x: Field) -> Field {
add2(add2(x))
}

fn main() {
let x: Field = pub_read()
pub_write(add4(add4(x)))
}
"#;
    let result = compile_triton(source, "test.tri");
    assert!(result.is_ok());
}

#[test]
fn test_all_binary_operators() {
    let source = r#"program test
fn main() {
let a: Field = pub_read()
let b: Field = pub_read()
let sum: Field = a + b
let prod: Field = a * b
let eq: Bool = a == b
let (hi, lo) = split(a)
let lt: Bool = hi < lo
let band: U32 = hi & lo
let bxor: U32 = hi ^ lo
let (q, r) = hi /% lo
pub_write(sum)
pub_write(prod)
}
"#;
    assert!(compile_triton(source, "test.tri").is_ok());
}

#[test]
fn test_struct_with_digest_field() {
    let source = r#"program test
struct AuthData {
owner: Digest,
nonce: Field,
}

fn main() {
let d: Digest = divine5()
let auth: AuthData = AuthData { owner: d, nonce: 42 }
pub_write(auth.nonce)
}
"#;
    assert!(compile_triton(source, "test.tri").is_ok());
}

#[test]
fn test_xfield_operations() {
    // *. operator is XField * Field -> XField (scalar multiplication)
    let source = r#"program test
fn main() {
let a: XField = xfield(1, 2, 3)
let s: Field = pub_read()
let c: XField = a *. s
let d: XField = xinvert(c)
pub_write(0)
}
"#;
    assert!(compile_triton(source, "test.tri").is_ok());
}

#[test]
fn test_tail_expression() {
    let source = r#"program test
fn double(x: Field) -> Field {
x + x
}

fn main() {
pub_write(double(pub_read()))
}
"#;
    assert!(compile_triton(source, "test.tri").is_ok());
}

#[test]
fn test_multiple_return_paths() {
    let source = r#"program test
fn abs_diff(a: Field, b: Field) -> Field {
if a == b {
    return 0
}
a + b
}

fn main() {
pub_write(abs_diff(pub_read(), pub_read()))
}
"#;
    assert!(compile_triton(source, "test.tri").is_ok());
}

#[test]
fn test_events_emit_and_seal() {
    let source = r#"program test

event Transfer {
from: Field,
to: Field,
amount: Field,
}

event Commitment {
value: Field,
}

fn main() {
let a: Field = pub_read()
let b: Field = pub_read()
let c: Field = pub_read()

// Open reveal: tag + 3 fields written directly
reveal Transfer { from: a, to: b, amount: c }

// Sealed: hash(tag, value, 0...) written as digest
seal Commitment { value: a }
}
"#;
    let tasm = compile_triton(source, "events.tri").expect("events program should compile");

    // reveal Transfer: push 0, write_io 1, [field], write_io 1 ร— 3
    // Total write_io 1 from reveal: 4 (tag + 3 fields)
    let write_io_1 = tasm.lines().filter(|l| l.trim() == "write_io 1").count();
    assert!(
        write_io_1 >= 4,
        "expected >= 4 write_io 1 (reveal tag + 3 fields), got {}",
        write_io_1
    );

    // seal Commitment: hash + write_io 5
    assert!(tasm.contains("hash"), "seal should contain hash");
    assert!(tasm.contains("write_io 5"), "seal should write_io 5");

    eprintln!("Events TASM:\n{}", tasm);
}

#[test]
fn test_error_max_nesting_depth() {
    // Generate deeply nested blocks via nested if statements.
    // Each `if true { ... }` adds one nesting level; 260 > MAX_NESTING_DEPTH (256).
    // The parser recurses to depth 256 before the guard triggers, which
    // needs more stack than the default test-thread provides in debug
    // builds.  Run the actual work on a thread with an explicit 16 MB stack.
    let handle = std::thread::Builder::new()
        .stack_size(16 * 1024 * 1024)
        .spawn(|| {
            let depth = 260u32;
            let mut src = String::from("program t\nfn main() {\n");
            for _ in 0..depth {
                src.push_str("if true {\n");
            }
            src.push_str("pub_write(0)\n");
            for _ in 0..depth {
                src.push_str("}\n");
            }
            src.push_str("}\n");

            let result = trident::parse_source_silent(&src, "t.tri");
            assert!(
                result.is_err(),
                "deeply nested input should produce an error"
            );
            let diags = result.unwrap_err();
            let has_depth = diags.iter().any(|d| d.message.contains("nesting depth"));
            assert!(
                has_depth,
                "should report nesting depth exceeded, got: {:?}",
                diags.iter().map(|d| &d.message).collect::<Vec<_>>()
            );
        })
        .expect("failed to spawn test thread");
    handle.join().expect("test thread panicked");
}

#[test]
fn test_coin_compiles() {
    let path = trident_repo_path("os/neptune/standards/coin.tri");
    if !path.exists() {
        return;
    }
    let tasm = compile_project_triton(&path).expect("coin program should compile");

    // Verify all 5 operations are in the TASM output
    assert!(tasm.contains("__pay:"), "missing pay function");
    assert!(tasm.contains("__mint:"), "missing mint function");
    assert!(tasm.contains("__burn:"), "missing burn function");
    assert!(tasm.contains("__lock:"), "missing lock function");
    assert!(tasm.contains("__update:"), "missing update function");

    // Verify helper functions
    assert!(tasm.contains("__hash_leaf:"), "missing hash_leaf function");
    assert!(
        tasm.contains("__hash_config:"),
        "missing hash_config function"
    );
    // hash_metadata is defined but never called โ€” DCE correctly removes it
    assert!(
        tasm.contains("__verify_auth:"),
        "missing verify_auth function"
    );
    assert!(
        tasm.contains("__verify_config:"),
        "missing verify_config function"
    );

    // Verify hash operations are emitted (leaf/config/auth + seal nullifiers)
    // hash_metadata is DCE'd (unused), so count is lower than pre-DCE
    let hash_count = tasm.lines().filter(|l| l.trim() == "hash").count();
    assert!(
        hash_count >= 5,
        "expected at least 5 hash ops, got {}",
        hash_count
    );

    // Verify seal produces write_io 5 (nullifier commitments in pay and burn)
    assert!(
        tasm.contains("write_io 5"),
        "seal should produce write_io 5"
    );

    // Verify assertions are present (security checks)
    let assert_count = tasm
        .lines()
        .filter(|l| l.trim().starts_with("assert"))
        .count();
    assert!(
        assert_count >= 6,
        "expected at least 6 assertions, got {}",
        assert_count
    );

    assert_plumb_v2_path_authentication(&tasm, plumb_vectors::coin_cases().remove(0));

    eprintln!(
        "Token TASM: {} lines, {} instructions",
        tasm.lines().count(),
        tasm.lines()
            .filter(|l| l.starts_with("    ") && !l.trim().is_empty())
            .count()
    );
}

#[test]
fn test_card_compiles() {
    let path = trident_repo_path("os/neptune/standards/card.tri");
    if !path.exists() {
        return;
    }
    let tasm = compile_project_triton(&path).expect("card program should compile");

    // Verify all 5 PLUMB operations
    assert!(tasm.contains("__pay:"), "missing pay function");
    assert!(tasm.contains("__mint:"), "missing mint function");
    assert!(tasm.contains("__burn:"), "missing burn function");
    assert!(tasm.contains("__lock:"), "missing lock function");
    assert!(tasm.contains("__update:"), "missing update function");

    // Verify helper functions
    assert!(tasm.contains("__hash_leaf:"), "missing hash_leaf function");
    assert!(
        tasm.contains("__hash_config:"),
        "missing hash_config function"
    );
    assert!(
        tasm.contains("__verify_auth:"),
        "missing verify_auth function"
    );
    assert!(
        tasm.contains("__verify_config:"),
        "missing verify_config function"
    );

    assert_plumb_v2_path_authentication(&tasm, plumb_vectors::card_cases().remove(0));
}

Graph