neo-decompiler 0.10.2

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::super::super::*;
use crate::instruction::OpCode;

#[test]
fn high_level_pick_of_literal_skips_temp() {
    // Script: PUSH1, PUSH2, PUSH1 (index), PICK, RET
    // PICK duplicates a simple literal (`1`), so — like DUP/OVER/TUCK —
    // it skips the temp materialization and just copies the value. No
    // `// pick stack[N]` comment is emitted (parity with the JS port's
    // `materialiseStackTopForDup`).
    let script = [
        OpCode::Push1.byte(),
        OpCode::Push2.byte(),
        OpCode::Push1.byte(),
        OpCode::Pick.byte(),
        OpCode::Ret.byte(),
    ];
    let nef_bytes = build_nef(&script);
    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("pick stack["),
        "literal PICK should skip the temp/comment: {high_level}"
    );
    assert!(
        !high_level.contains("insufficient values on stack for PICK"),
        "literal PICK must not underflow: {high_level}"
    );
}

#[test]
fn high_level_pick_of_side_effecting_value_materializes_temp() {
    // Script: SYSCALL(System.Runtime.GetTime) ; PUSH0 (index) ; PICK ;
    //         ADD ; RET
    // PICK duplicates the syscall result (a side-effecting expression).
    // It must be hoisted into a temp so the call is evaluated once and
    // referenced twice — not string-copied (which would emit two
    // syscalls).
    let script = [
        OpCode::Syscall.byte(),
        0xB7,
        0xC3,
        0x88,
        0x03,                 // SYSCALL System.Runtime.GetTime
        OpCode::Push0.byte(), // PUSH0 (index)
        OpCode::Pick.byte(),  // PICK
        OpCode::Add.byte(),   // ADD
        OpCode::Ret.byte(),   // RET
    ];
    let nef_bytes = build_nef(&script);
    let decompilation = Decompiler::new()
        .decompile_bytes(&nef_bytes)
        .expect("decompile succeeds");

    let high_level = decompilation
        .high_level
        .as_deref()
        .expect("high-level output");
    let syscall_occurrences = high_level.matches("System.Runtime.GetTime").count();
    assert_eq!(
        syscall_occurrences, 1,
        "PICK of a syscall result must evaluate it once (materialized temp): {high_level}"
    );
}

#[test]
fn high_level_lifts_xdrop_with_literal_index() {
    // Script: PUSH1, PUSH2, PUSH3, PUSH1 (index), XDROP, RET
    // XDROP uses the literal index (1) to remove the second item from the
    // top. The lifted statement keeps the post-XDROP stack right (PUSH3
    // bubbles to the top) — the informational `// xdrop stack[N]`
    // comment that used to surface here was just noise duplicating
    // what the surrounding statements already convey, and `strip_stack_comments`
    // now drops it for parity with the JS port.
    let script = [
        OpCode::Push1.byte(),
        OpCode::Push2.byte(),
        OpCode::Push3.byte(),
        OpCode::Push1.byte(),
        OpCode::Xdrop.byte(),
        OpCode::Ret.byte(),
    ];
    let nef_bytes = build_nef(&script);
    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("return t2;") || high_level.contains("return 3;"),
        "XDROP should preserve the top value (PUSH3): {high_level}"
    );
    assert!(
        !high_level.contains("// xdrop stack"),
        "informational xdrop comment should be stripped: {high_level}"
    );
}

#[test]
fn high_level_pick_preserves_packed_shape_for_unpack_reverse4() {
    // Script:
    //   INITSLOT 1,0
    //   PUSH1; PUSH2; PUSH2; PACK; STLOC0
    //   PUSH3; LDLOC0; PUSH0; PICK; UNPACK; DROP; REVERSE4; RET
    let script = [
        OpCode::Initslot.byte(),
        0x01,
        0x00, // INITSLOT 1 local, 0 args
        OpCode::Push1.byte(),
        OpCode::Push2.byte(),
        OpCode::Push2.byte(),
        OpCode::Pack.byte(),
        OpCode::Stloc0.byte(), // PUSH1; PUSH2; PUSH2; PACK; STLOC0
        OpCode::Push3.byte(),
        OpCode::Ldloc0.byte(),
        OpCode::Push0.byte(),
        OpCode::Pick.byte(),
        OpCode::Unpack.byte(),
        OpCode::Drop.byte(),
        OpCode::Reverse4.byte(),
        OpCode::Ret.byte(), // PUSH3; LDLOC0; PUSH0; PICK; UNPACK; DROP; REVERSE4; RET
    ];
    let nef_bytes = build_nef(&script);
    let decompilation = Decompiler::new()
        .decompile_bytes(&nef_bytes)
        .expect("decompile succeeds");

    let high_level = decompilation
        .high_level
        .as_deref()
        .expect("high-level output");
    // The previous "reverse top 4 stack values" check was VM
    // narration — stripped from clean output now. The substantive
    // check below ensures REVERSE4 didn't underflow after the
    // PICK→UNPACK chain.
    assert!(
        !high_level.contains("insufficient values on stack for REVERSE4"),
        "PICK should preserve packed shape metadata for downstream UNPACK stack modeling: {high_level}"
    );
}