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::*;

#[test]
fn decompile_infers_entry_stack_argument_for_syscall_only_helper() {
    // Script layout:
    // 0x0000: PUSHDATA1 "x"
    // 0x0003: CALL +3 (target = 0x0006)
    // 0x0005: RET
    // 0x0006: SYSCALL System.Runtime.Log
    // 0x000B: RET
    let nef_bytes = build_nef(&[
        0x0C, 0x01, b'x', // PUSHDATA1 "x"
        0x34, 0x03, // CALL +3
        0x40, // RET
        0x41, 0xCF, 0xE7, 0x47, 0x96, // SYSCALL System.Runtime.Log
        0x40, // RET
    ]);
    let decompilation = Decompiler::new()
        .decompile_bytes(&nef_bytes)
        .expect("decompile succeeds");

    let high_level = decompilation
        .high_level
        .as_deref()
        .expect("high-level output");
    assert!(
        high_level.contains("fn sub_0x0006(arg0)"),
        "syscall-only helper should infer one entry-stack argument: {high_level}"
    );
    assert!(
        high_level.contains("System.Runtime.Log(arg0)"),
        "syscall-only helper should consume inferred argument instead of ???: {high_level}"
    );
    assert!(
        !decompilation
            .warnings
            .iter()
            .any(|warning| warning
                .contains("missing syscall argument values for System.Runtime.Log")),
        "syscall-only helper should not emit missing-argument warnings: {:?}",
        decompilation.warnings
    );
}

#[test]
fn decompile_infers_entry_stack_arguments_for_packmap_helper() {
    // Script layout:
    // 0x0000..0x0003: PUSH4 ; PUSH3 ; PUSH2 ; PUSH1
    // 0x0004:         CALL +3 (target = 0x0007)
    // 0x0006:         RET
    // 0x0007:         PUSH2 ; PACKMAP ; RET
    let nef_bytes = build_nef(&[
        0x14, 0x13, 0x12, 0x11, // caller supplies four map key/value slots
        0x34, 0x03, // CALL +3
        0x40, // RET
        0x12, 0xBE, // PUSH2 ; PACKMAP
        0x40, // RET
    ]);
    let decompilation = Decompiler::new()
        .decompile_bytes(&nef_bytes)
        .expect("decompile succeeds");

    let high_level = decompilation
        .high_level
        .as_deref()
        .expect("high-level output");
    assert!(
        high_level.contains("fn sub_0x0007(arg0, arg1, arg2, arg3)"),
        "PACKMAP helper should infer four entry-stack arguments: {high_level}"
    );
    assert!(
        high_level.contains("Map("),
        "PACKMAP helper should render a map expression: {high_level}"
    );
    assert!(
        !high_level.contains("???"),
        "PACKMAP helper should consume inferred arguments instead of placeholders: {high_level}"
    );
}

#[test]
fn decompile_infers_entry_stack_arguments_for_memcpy_helper() {
    // Script layout:
    // 0x0000..0x0004: PUSH1 x5
    // 0x0005:         CALL +3 (target = 0x0008)
    // 0x0007:         RET
    // 0x0008:         MEMCPY ; RET
    let nef_bytes = build_nef(&[
        0x11, 0x11, 0x11, 0x11, 0x11, // caller supplies MEMCPY's five operands
        0x34, 0x03, // CALL +3
        0x40, // RET
        0x89, // MEMCPY
        0x40, // RET
    ]);
    let decompilation = Decompiler::new()
        .decompile_bytes(&nef_bytes)
        .expect("decompile succeeds");

    let high_level = decompilation
        .high_level
        .as_deref()
        .expect("high-level output");
    assert!(
        high_level.contains("fn sub_0x0008(arg0, arg1, arg2, arg3, arg4)"),
        "MEMCPY helper should infer five entry-stack arguments: {high_level}"
    );
    assert!(
        high_level.contains("memcpy("),
        "MEMCPY helper should render the memcpy helper call: {high_level}"
    );
    assert!(
        !high_level.contains("???"),
        "MEMCPY helper should consume inferred arguments instead of placeholders: {high_level}"
    );
}

#[test]
fn decompile_infers_entry_stack_argument_for_popitem_helper() {
    // Script layout:
    // 0x0000: NEWARRAY0
    // 0x0001: CALL +3 (target = 0x0004)
    // 0x0003: RET
    // 0x0004: POPITEM ; RET
    let nef_bytes = build_nef(&[
        0xC2, // NEWARRAY0
        0x34, 0x03, // CALL +3
        0x40, // RET
        0xD4, // POPITEM
        0x40, // RET
    ]);
    let decompilation = Decompiler::new()
        .decompile_bytes(&nef_bytes)
        .expect("decompile succeeds");

    let high_level = decompilation
        .high_level
        .as_deref()
        .expect("high-level output");
    assert!(
        high_level.contains("fn sub_0x0004(arg0)"),
        "POPITEM helper should infer one entry-stack argument: {high_level}"
    );
    assert!(
        high_level.contains("pop_item(arg0)"),
        "POPITEM helper should consume the inferred collection argument: {high_level}"
    );
    assert!(
        !high_level.contains("???"),
        "POPITEM helper should not invent a second argument placeholder: {high_level}"
    );
}

#[test]
fn decompile_infers_entry_stack_arguments_for_pick_helper() {
    // Helper body: PUSH1 ; PICK ; RET. PICK consumes the literal index and
    // duplicates the second value from the top, so the helper needs two
    // caller-supplied values in addition to its local index literal.
    let nef_bytes = build_nef(&[
        0x12, 0x11, // caller supplies two stack values
        0x34, 0x03, // CALL +3 (target = 0x0005)
        0x40, // RET
        0x11, 0x4D, // PUSH1 ; PICK
        0x40, // RET
    ]);
    let decompilation = Decompiler::new()
        .decompile_bytes(&nef_bytes)
        .expect("decompile succeeds");

    let high_level = decompilation
        .high_level
        .as_deref()
        .expect("high-level output");
    assert!(
        high_level.contains("fn sub_0x0005(arg0, arg1)"),
        "PICK helper should infer two entry-stack arguments: {high_level}"
    );
    assert!(
        !high_level.contains("???"),
        "PICK helper should consume inferred arguments instead of placeholders: {high_level}"
    );
}

#[test]
fn decompile_infers_entry_stack_arguments_for_roll_helper() {
    // Helper body: PUSH1 ; ROLL ; RET. ROLL consumes the literal index and
    // moves the second value from the top to the top, so the helper needs two
    // caller-supplied values.
    let nef_bytes = build_nef(&[
        0x12, 0x11, // caller supplies two stack values
        0x34, 0x03, // CALL +3 (target = 0x0005)
        0x40, // RET
        0x11, 0x52, // PUSH1 ; ROLL
        0x40, // RET
    ]);
    let decompilation = Decompiler::new()
        .decompile_bytes(&nef_bytes)
        .expect("decompile succeeds");

    let high_level = decompilation
        .high_level
        .as_deref()
        .expect("high-level output");
    assert!(
        high_level.contains("fn sub_0x0005(arg0, arg1)"),
        "ROLL helper should infer two entry-stack arguments: {high_level}"
    );
    assert!(
        !high_level.contains("???"),
        "ROLL helper should consume inferred arguments instead of placeholders: {high_level}"
    );
}

#[test]
fn decompile_infers_entry_stack_arguments_for_xdrop_helper() {
    // Helper body: PUSH1 ; XDROP ; RET. XDROP consumes the literal index and
    // removes the second value from the top, so the helper needs two
    // caller-supplied values.
    let nef_bytes = build_nef(&[
        0x12, 0x11, // caller supplies two stack values
        0x34, 0x03, // CALL +3 (target = 0x0005)
        0x40, // RET
        0x11, 0x48, // PUSH1 ; XDROP
        0x40, // RET
    ]);
    let decompilation = Decompiler::new()
        .decompile_bytes(&nef_bytes)
        .expect("decompile succeeds");

    let high_level = decompilation
        .high_level
        .as_deref()
        .expect("high-level output");
    assert!(
        high_level.contains("fn sub_0x0005(arg0, arg1)"),
        "XDROP helper should infer two entry-stack arguments: {high_level}"
    );
    assert!(
        !high_level.contains("???"),
        "XDROP helper should consume inferred arguments instead of placeholders: {high_level}"
    );
}

#[test]
fn decompile_infers_entry_stack_arguments_for_reversen_helper() {
    // Helper body: PUSH3 ; REVERSEN ; RET. REVERSEN consumes the literal count
    // and reverses that many existing stack values.
    let nef_bytes = build_nef(&[
        0x13, 0x12, 0x11, // caller supplies three stack values
        0x34, 0x03, // CALL +3 (target = 0x0006)
        0x40, // RET
        0x13, 0x55, // PUSH3 ; REVERSEN
        0x40, // RET
    ]);
    let decompilation = Decompiler::new()
        .decompile_bytes(&nef_bytes)
        .expect("decompile succeeds");

    let high_level = decompilation
        .high_level
        .as_deref()
        .expect("high-level output");
    assert!(
        high_level.contains("fn sub_0x0006(arg0, arg1, arg2)"),
        "REVERSEN helper should infer three entry-stack arguments: {high_level}"
    );
    assert!(
        !high_level.contains("???"),
        "REVERSEN helper should consume inferred arguments instead of placeholders: {high_level}"
    );
}