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, OperandEncoding};

#[test]
fn high_level_dispatch_handles_every_known_opcode_without_generic_placeholder() {
    for opcode in OpCode::all_known() {
        let script = opcode_matrix_script(opcode);
        let nef_bytes = build_nef(&script);
        let decompilation = Decompiler::new()
            .decompile_bytes(&nef_bytes)
            .unwrap_or_else(|err| panic!("decompile should accept {opcode}: {err}"));
        let high_level = decompilation
            .high_level
            .as_deref()
            .expect("high-level output");

        assert!(
            !high_level.contains("not yet translated"),
            "{opcode} should be handled by high-level dispatch, got:\n{high_level}"
        );
    }
}

fn opcode_matrix_script(opcode: OpCode) -> Vec<u8> {
    let mut script = Vec::new();
    script.extend_from_slice(&opcode_stack_setup(opcode));
    script.push(opcode.byte());
    script.extend_from_slice(&sample_operand_bytes(opcode.operand_encoding()));
    if opcode != OpCode::Ret {
        script.push(OpCode::Ret.byte());
    }
    script
}

fn opcode_stack_setup(opcode: OpCode) -> Vec<u8> {
    use OpCode::*;

    match opcode {
        Assert | Throw | Abortmsg | Newarray | NewarrayT | Newbuffer | Newstruct | Size | Keys
        | Values | Isnull | Istype | Convert | Xdrop | Pick | Roll | Reversen => {
            vec![Push1.byte()]
        }
        Assertmsg | Cat | Substr | Left | Right | And | Or | Xor | Equal | Notequal | Add | Sub
        | Mul | Div | Mod | Pow | Shl | Shr | Booland | Boolor | Numequal | Numnotequal | Lt
        | Le | Gt | Ge | Min | Max | Haskey | Pickitem | Append | Remove | Popitem | Jmpif
        | Jmpif_L | Jmpifnot | Jmpifnot_L => vec![Push1.byte(), Push2.byte()],
        Modmul | Modpow | Within | Setitem => vec![Push1.byte(), Push2.byte(), Push3.byte()],
        Memcpy => vec![
            Push1.byte(),
            Push2.byte(),
            Push3.byte(),
            Push4.byte(),
            Push5.byte(),
        ],
        Pack | Packmap | Packstruct => vec![Push1.byte(), Push2.byte(), Push2.byte()],
        Unpack => vec![Push2.byte(), Push3.byte(), Push2.byte(), Pack.byte()],
        Reverse3 => vec![Push1.byte(), Push2.byte(), Push3.byte()],
        Reverse4 => vec![Push1.byte(), Push2.byte(), Push3.byte(), Push4.byte()],
        Nip | Over | Swap | Tuck => vec![Push1.byte(), Push2.byte()],
        Rot => vec![Push1.byte(), Push2.byte(), Push3.byte()],
        CallA => vec![PushA.byte(), 0x02, 0x00, 0x00, 0x00],
        JmpEq | JmpEq_L | JmpNe | JmpNe_L | JmpGt | JmpGt_L | JmpGe | JmpGe_L | JmpLt | JmpLt_L
        | JmpLe | JmpLe_L => vec![Push1.byte(), Push2.byte()],
        Stloc0 | Stloc1 | Stloc2 | Stloc3 | Stloc4 | Stloc5 | Stloc6 | Stloc | Starg0 | Starg1
        | Starg2 | Starg3 | Starg4 | Starg5 | Starg6 | Starg | Stsfld0 | Stsfld1 | Stsfld2
        | Stsfld3 | Stsfld4 | Stsfld5 | Stsfld6 | Stsfld => vec![Push1.byte()],
        _ => Vec::new(),
    }
}

fn sample_operand_bytes(encoding: OperandEncoding) -> Vec<u8> {
    match encoding {
        OperandEncoding::None => Vec::new(),
        OperandEncoding::I8 => vec![1],
        OperandEncoding::I16 => 1i16.to_le_bytes().to_vec(),
        OperandEncoding::I32 => 1i32.to_le_bytes().to_vec(),
        OperandEncoding::I64 => 1i64.to_le_bytes().to_vec(),
        OperandEncoding::Bytes(len) => vec![0; len],
        OperandEncoding::Data1 => vec![1, b'a'],
        OperandEncoding::Data2 => {
            let mut bytes = 1u16.to_le_bytes().to_vec();
            bytes.push(b'a');
            bytes
        }
        OperandEncoding::Data4 => {
            let mut bytes = 1u32.to_le_bytes().to_vec();
            bytes.push(b'a');
            bytes
        }
        OperandEncoding::Jump8 => vec![1],
        OperandEncoding::Jump32 => 1i32.to_le_bytes().to_vec(),
        OperandEncoding::U8 => vec![0],
        OperandEncoding::U16 => 0u16.to_le_bytes().to_vec(),
        OperandEncoding::U32 => 1u32.to_le_bytes().to_vec(),
        OperandEncoding::Syscall => 0u32.to_le_bytes().to_vec(),
    }
}