neo-decompiler 0.11.0

Neo N3 NEF decompiler: parse, disassemble, lift bytecode to high-level pseudocode and C# skeletons, with a CLI, JSON reports, and optional WebAssembly bindings.
Documentation
use super::*;
use crate::instruction::OpCode;

#[test]
fn csharp_resolves_internal_calls_to_method_names() {
    // Script layout:
    // 0x0000: CALL +4 (target=0x0004)
    // 0x0002: RET
    // 0x0003: NOP
    // 0x0004: RET
    let script = [
        OpCode::Call.byte(),
        0x04,
        OpCode::Ret.byte(),
        OpCode::Nop.byte(),
        OpCode::Ret.byte(),
    ];
    let nef_bytes = build_nef(&script);

    let decompilation = Decompiler::new()
        .decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::All)
        .expect("decompile succeeds");

    let csharp = decompilation.csharp.as_deref().expect("csharp output");
    assert!(
        csharp.contains("sub_0x0004()"),
        "C# output should resolve internal helper names instead of raw call placeholders: {csharp}"
    );
    assert!(
        !csharp.contains("call_0x0004"),
        "C# output should not emit raw call_0x placeholders when a helper name is known: {csharp}"
    );
}

#[test]
fn csharp_emits_inferred_helper_methods() {
    // Script layout:
    // 0x0000: CALL +4 (target=0x0004)
    // 0x0002: RET
    // 0x0003: NOP
    // 0x0004: RET
    let script = [
        OpCode::Call.byte(),
        0x04,
        OpCode::Ret.byte(),
        OpCode::Nop.byte(),
        OpCode::Ret.byte(),
    ];
    let nef_bytes = build_nef(&script);

    let decompilation = Decompiler::new()
        .decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::All)
        .expect("decompile succeeds");

    let csharp = decompilation.csharp.as_deref().expect("csharp output");
    assert!(
        csharp.contains("private static dynamic sub_0x0004()")
            || csharp.contains("private static void sub_0x0004()")
            || csharp.contains("private static BigInteger sub_0x0004()")
            || csharp.contains("private static object sub_0x0004()"),
        "C# output should emit inferred helper method definitions for resolved internal calls: {csharp}"
    );
    assert!(
        !csharp.contains("sub_0x0003"),
        "C# output should not emit nop-only inferred helper methods: {csharp}"
    );
}

#[test]
fn csharp_inferred_nonvoid_helpers_do_not_emit_bare_return() {
    // Script layout:
    // 0x0000: CALL +4 (target=0x0004)
    // 0x0002: RET
    // 0x0003: NOP
    // 0x0004: RET
    let script = [
        OpCode::Call.byte(),
        0x04,
        OpCode::Ret.byte(),
        OpCode::Nop.byte(),
        OpCode::Ret.byte(),
    ];
    let nef_bytes = build_nef(&script);

    let decompilation = Decompiler::new()
        .decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::All)
        .expect("decompile succeeds");

    let csharp = decompilation.csharp.as_deref().expect("csharp output");
    assert!(
        !csharp.contains(
            "private static dynamic sub_0x0004()
        {
            // 0004: RET
            return;"
        ),
        "non-void inferred helper bodies should not emit bare return statements: {csharp}"
    );
}

#[test]
fn csharp_trims_initslot_boundaries() {
    let Some(nef_bytes) = try_load_testing_nef("Contract_Delegate.nef") else {
        eprintln!("Skipping: Contract_Delegate.nef not found in devpack artifacts");
        return;
    };
    let Some(manifest) = try_load_testing_manifest("Contract_Delegate.manifest.json") else {
        eprintln!("Skipping: Contract_Delegate.manifest.json not found");
        return;
    };

    let decompilation = Decompiler::new()
        .decompile_bytes_with_manifest(&nef_bytes, Some(manifest), OutputFormat::All)
        .expect("decompile succeeds");

    let csharp = decompilation.csharp.as_deref().expect("csharp output");
    let sum_block = csharp
        .split("public static BigInteger sumFunc")
        .nth(1)
        .and_then(|rest| rest.split("private static dynamic sub_0x000C").next())
        .expect("sumFunc block present");
    assert!(
        sum_block.contains("// 0000: INITSLOT"),
        "sumFunc should still show its entry INITSLOT"
    );
    assert!(
        !sum_block.contains("// 000C: INITSLOT"),
        "sumFunc body should stop before the inferred helper block"
    );
    assert!(
        !sum_block.contains("return t23;"),
        "duplicate return from appended block should not appear in sumFunc"
    );
    assert!(
        csharp.contains("private static dynamic sub_0x000C"),
        "inferred helper should now be emitted separately"
    );
}