neo-decompiler 0.8.1

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 parses_valid_nef() {
    let script = vec![0x10, 0x11, 0x40];
    let bytes = build_sample(&script);
    let nef = NefParser::new().parse(&bytes).expect("parse succeeds");

    assert_eq!(nef.header.magic, MAGIC);
    assert_eq!(nef.header.compiler, "neo-sample");
    assert!(nef.header.source.is_empty());
    assert_eq!(nef.script, script);
    assert!(nef.method_tokens.is_empty());
    assert_eq!(
        util::format_hash(&nef.script_hash()),
        util::format_hash(&util::hash160(&[0x10, 0x11, 0x40]))
    );
}

#[test]
fn rejects_bad_magic() {
    let mut bytes = build_sample(&[0x40]);
    bytes[0] = b'X';
    let err = NefParser::new().parse(&bytes).unwrap_err();
    assert!(matches!(
        err,
        crate::error::Error::Nef(NefError::InvalidMagic { .. })
    ));
}

#[test]
fn rejects_bad_checksum() {
    let mut bytes = build_sample(&[0x40]);
    let last = bytes.len() - 1;
    bytes[last] ^= 0xFF;
    let err = NefParser::new().parse(&bytes).unwrap_err();
    assert!(matches!(
        err,
        crate::error::Error::Nef(NefError::ChecksumMismatch { .. })
    ));
}

#[test]
fn rejects_truncated_checksum_instead_of_panicking() {
    let mut bytes = build_sample(&[0x40]);
    bytes.pop(); // drop part of the checksum trailer
    let err = NefParser::new().parse(&bytes).unwrap_err();
    assert!(matches!(
        err,
        crate::error::Error::Nef(NefError::UnexpectedEof { .. })
    ));
}

#[test]
fn rejects_trailing_bytes() {
    let script = vec![0x40];
    let bytes = build_sample(&script);
    let mut with_extra = bytes;
    with_extra.push(0x99);

    let err = NefParser::new().parse(&with_extra).unwrap_err();
    assert!(matches!(
        err,
        crate::error::Error::Nef(NefError::TrailingData { extra: 1 })
    ));
}

#[test]
fn rejects_nonzero_reserved_byte() {
    let mut bytes = build_sample(&[0x40]);
    bytes[69] = 0x01;

    let err = NefParser::new().parse(&bytes).unwrap_err();
    assert!(matches!(
        err,
        crate::error::Error::Nef(NefError::ReservedByteNonZero {
            offset: 69,
            value: 0x01
        })
    ));
}

#[test]
fn rejects_nonzero_reserved_word() {
    let mut bytes = build_sample(&[0x40]);
    let reserved_word_offset = 4 + 64 + 1 + 1 + 1;
    bytes[reserved_word_offset] = 0x34;
    bytes[reserved_word_offset + 1] = 0x12;
    let checksum = NefParser::calculate_checksum(&bytes[..bytes.len() - 4]);
    let checksum_offset = bytes.len() - 4;
    bytes[checksum_offset..].copy_from_slice(&checksum.to_le_bytes());

    let err = NefParser::new().parse(&bytes).unwrap_err();
    assert!(matches!(
        err,
        crate::error::Error::Nef(NefError::ReservedWordNonZero { offset, value: 0x1234 })
            if offset == reserved_word_offset
    ));
}

#[test]
fn rejects_oversized_u64_varint_for_source_length() {
    let script = vec![0x40];
    let mut data = Vec::new();
    data.extend_from_slice(&MAGIC);
    data.extend_from_slice(&[0u8; 64]);
    data.push(0xFF);
    data.extend_from_slice(&(u32::MAX as u64 + 1).to_le_bytes());
    data.push(0);
    data.push(0);
    data.extend_from_slice(&0u16.to_le_bytes());
    write_varint(&mut data, script.len() as u32);
    data.extend_from_slice(&script);
    let checksum = NefParser::calculate_checksum(&data);
    data.extend_from_slice(&checksum.to_le_bytes());

    let err = NefParser::new().parse(&data).unwrap_err();
    assert!(matches!(
        err,
        crate::error::Error::Nef(NefError::IntegerOverflow { offset: 68 })
    ));
}

#[test]
fn accepts_non_canonical_varint_for_source_length() {
    // The reference MemoryReader.ReadVarInt has no canonicality check: the
    // prefix selects the width and only the value range is validated, so a
    // source length of 5 encoded as `FD 05 00` must parse as 5.
    let script = vec![0x40];
    let mut data = Vec::new();
    data.extend_from_slice(&MAGIC);
    data.extend_from_slice(&[0u8; 64]);
    data.push(0xFD);
    data.extend_from_slice(&5u16.to_le_bytes());
    data.extend_from_slice(b"src:5");
    data.push(0); // reserved byte
    data.push(0); // zero tokens
    data.extend_from_slice(&0u16.to_le_bytes());
    write_varint(&mut data, script.len() as u32);
    data.extend_from_slice(&script);
    let checksum = NefParser::calculate_checksum(&data);
    data.extend_from_slice(&checksum.to_le_bytes());

    let nef = NefParser::new().parse(&data).expect("parse succeeds");
    assert_eq!(nef.header.source, "src:5");
    assert_eq!(nef.script, script);
}

#[test]
fn rejects_leading_0xff_magic() {
    // Cross-port vector: a 0xFF-leading file must fail magic validation
    // (and not crash on text decoding, as the JS port once did).
    let mut bytes = build_sample(&[0x40]);
    bytes[0] = 0xFF;
    let err = NefParser::new().parse(&bytes).unwrap_err();
    assert!(matches!(
        err,
        crate::error::Error::Nef(NefError::InvalidMagic { .. })
    ));
}