use super::*;
#[test]
fn disassemble_bytes_returns_instruction_stream_without_rendering() {
let nef_bytes = sample_nef();
let output = Decompiler::new()
.disassemble_bytes(&nef_bytes)
.expect("disassembly succeeds");
assert_eq!(output.instructions.len(), 4);
assert!(output.warnings.is_empty());
assert_eq!(output.instructions[0].offset, 0);
assert_eq!(output.instructions[1].offset, 1);
}
#[test]
fn decompile_end_to_end() {
let nef_bytes = sample_nef();
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
assert_eq!(decompilation.instructions.len(), 4);
assert!(decompilation
.pseudocode
.as_deref()
.expect("pseudocode output")
.contains("ADD"));
assert!(decompilation
.high_level
.as_deref()
.expect("high-level output")
.contains("contract NeoContract"));
assert!(decompilation
.high_level
.as_deref()
.expect("high-level output")
.contains("fn script_entry()"));
}
#[test]
fn decompile_with_manifest_produces_contract_name() {
let nef_bytes = sample_nef();
let manifest = sample_manifest();
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, Some(manifest), OutputFormat::All)
.expect("decompile succeeds with manifest");
assert!(decompilation
.high_level
.as_deref()
.expect("high-level output")
.contains("contract ExampleContract"));
assert!(decompilation
.high_level
.as_deref()
.expect("high-level output")
.contains("fn main() -> int {"));
}
#[test]
fn cfg_to_dot_includes_contract_name_and_script_hash_in_label() {
let nef_bytes = sample_nef();
let manifest = sample_manifest();
let with = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, Some(manifest), OutputFormat::Pseudocode)
.expect("decompile with manifest");
let dot_with = with.cfg_to_dot();
assert!(dot_with.starts_with("digraph CFG {\n label=\""));
assert!(dot_with.contains("ExampleContract"), "{dot_with}");
assert!(dot_with.contains("instr)"), "{dot_with}");
assert!(dot_with.contains("labelloc=\"t\";"), "{dot_with}");
let without = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile without manifest");
let dot_without = without.cfg_to_dot();
assert!(dot_without.contains("label=\""), "{dot_without}");
assert!(!dot_without.contains("ExampleContract"), "{dot_without}");
assert!(dot_without.contains("instr)"), "{dot_without}");
}
#[test]
fn decompile_lifts_indirect_calls_without_not_yet_translated_warning() {
let nef_bytes = build_nef(&[0x36, 0x37, 0x01, 0x00, 0x40]);
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("calla("),
"CALLA should be lifted to an indirect-call statement: {high_level}"
);
assert!(
high_level.contains("callt(0x0001)"),
"CALLT should be lifted to an indirect-call statement: {high_level}"
);
assert!(
!high_level.contains("not yet translated"),
"indirect calls should no longer emit not-yet-translated placeholders: {high_level}"
);
}
#[test]
fn decompile_uses_method_token_signature_for_callt_arguments_and_returns() {
let nef_bytes = build_nef_with_single_token(
&[0x11, 0x37, 0x00, 0x00, 0x40],
[0u8; 20],
"foo",
1,
false,
0x0F,
);
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("foo(t0);"),
"CALLT should consume declared token argument and emit call expression: {high_level}"
);
assert!(
!high_level.contains("let t1 = foo("),
"CALLT token marked non-returning should not push a synthetic return temp: {high_level}"
);
assert!(
high_level.contains("return;"),
"script entry should end with a bare return after non-returning CALLT: {high_level}"
);
}
#[test]
fn decompile_lifts_relative_calls_without_control_flow_warning() {
let nef_bytes = build_nef(&[0x34, 0x02, 0x35, 0x05, 0x00, 0x00, 0x00, 0x40]);
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("sub_0x0002()"),
"CALL should resolve to inferred method name: {high_level}"
);
assert!(
high_level.contains("sub_0x0007()"),
"CALL_L should resolve to inferred method name: {high_level}"
);
assert!(
!high_level.contains("control flow not yet lifted"),
"relative calls should no longer use control-flow-not-lifted warnings: {high_level}"
);
}
#[test]
fn decompile_resolves_relative_call_target_to_inferred_method_name() {
let nef_bytes = build_nef(&[
0x34, 0x05, 0x40, 0x21, 0x21, 0x57, 0x00, 0x00, 0x40, ]);
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("sub_0x0005()"),
"relative CALL should use inferred callee name when target matches method start: {high_level}"
);
}
#[test]
fn decompile_relative_call_passes_known_method_arguments() {
let nef_bytes = build_nef(&[
0x11, 0x34, 0x07, 0x40, 0x21, 0x21, 0x21, 0x21, 0x57, 0x00, 0x01, 0x78, 0x40, ]);
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("sub_0x0008("),
"relative CALL should target inferred method call syntax: {high_level}"
);
assert!(
!high_level.contains("sub_0x0008()"),
"relative CALL into one-arg method should pass argument expression: {high_level}"
);
}
#[test]
fn decompile_infers_entry_stack_argument_for_syscall_only_helper() {
let nef_bytes = build_nef(&[
0x0C, 0x01, b'x', 0x34, 0x03, 0x40, 0x41, 0xCF, 0xE7, 0x47, 0x96, 0x40, ]);
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("syscall(\"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_lifts_unconditional_jumps_without_control_flow_warning() {
let nef_bytes = build_nef(&[0x22, 0x02, 0x23, 0x05, 0x00, 0x00, 0x00, 0x40]);
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("control flow not yet lifted"),
"unconditional jumps should no longer emit control-flow-not-lifted warnings: {high_level}"
);
assert!(
!high_level.contains("label_0x0002:"),
"fallthrough JMP target should be removed as orphan: {high_level}"
);
assert!(
!high_level.contains("label_0x0007:"),
"fallthrough JMP_L target should be removed as orphan: {high_level}"
);
}
#[test]
fn decompile_manifestless_entry_surfaces_initslot_args() {
let script = [0x57, 0x02, 0x01, 0x78, 0x70, 0x68, 0x40];
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("fn script_entry(arg0)"),
"manifest-less script_entry should expose INITSLOT-declared args: {high_level}"
);
assert!(
high_level.contains("let loc0 = arg0;"),
"body should reference the same arg label as the signature: {high_level}"
);
}
#[test]
fn decompile_known_syscall_drops_redundant_hash_comment_in_clean_mode() {
let script = [0x41, 0xB7, 0xC3, 0x88, 0x03, 0x75, 0x40];
let nef_bytes = build_nef(&script);
let trace = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds (trace)");
let trace_high = trace
.high_level
.as_deref()
.expect("high-level output (trace)");
assert!(
trace_high.contains("// 0x0388C3B7"),
"trace mode should keep the syscall hash comment: {trace_high}"
);
let clean = Decompiler::new()
.with_trace_comments(false)
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds (clean)");
let clean_high = clean
.high_level
.as_deref()
.expect("high-level output (clean)");
assert!(
!clean_high.contains("// 0x0388C3B7"),
"clean mode should drop the redundant syscall hash comment: {clean_high}"
);
assert!(
clean_high.contains("System.Runtime.GetTime"),
"syscall name must still appear in the call expression: {clean_high}"
);
}
#[test]
fn decompile_unknown_syscall_keeps_unknown_annotation() {
let script = [0x41, 0xEF, 0xBE, 0xAD, 0xDE, 0x75, 0x40]; let nef_bytes = build_nef(&script);
for emit_trace in [true, false] {
let decompilation = Decompiler::new()
.with_trace_comments(emit_trace)
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(
high.contains("// warning: unknown syscall 0xDEADBEEF"),
"unknown-syscall annotation must always be emitted with the JS-style \
`// warning:` prefix (trace={emit_trace}): {high}"
);
assert!(
high.contains("0xDEADBEEF"),
"unknown-syscall hash must appear in the call expression itself: {high}"
);
assert!(
decompilation
.warnings
.iter()
.any(|w| w.contains("unknown syscall 0xDEADBEEF")),
"unknown-syscall warning must be surfaced via the warnings array (trace={emit_trace}): {:?}",
decompilation.warnings,
);
}
}
#[test]
fn decompile_lifts_endtry_transfers_without_control_flow_warning() {
let nef_bytes = build_nef(&[0x3D, 0x02, 0x3E, 0x05, 0x00, 0x00, 0x00, 0x40]);
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("control flow not yet lifted"),
"ENDTRY opcodes should no longer emit control-flow-not-lifted warnings: {high_level}"
);
assert!(
!high_level.contains("leave label_"),
"fallthrough ENDTRY transfers should be eliminated: {high_level}"
);
assert!(
!high_level.contains("label_0x0002:"),
"fallthrough ENDTRY target should be removed as orphan: {high_level}"
);
assert!(
!high_level.contains("label_0x0007:"),
"fallthrough ENDTRY_L target should be removed as orphan: {high_level}"
);
assert!(
high_level.contains("return;"),
"method body should still emit the trailing return: {high_level}"
);
}
#[test]
fn decompile_uses_label_style_for_unresolved_jump_targets() {
let nef_bytes = build_nef(&[0x22, 0x05, 0x40]);
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("goto label_0x0005;"),
"unresolved jump target should still use label-style transfer naming: {high_level}"
);
assert!(
!high_level.contains("goto_0x0005();"),
"legacy function-style jump placeholder should not be emitted: {high_level}"
);
}
#[test]
fn decompile_uses_label_style_for_unresolved_endtry_targets() {
let nef_bytes = build_nef(&[0x3D, 0x05, 0x40]);
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("leave label_0x0005;"),
"unresolved endtry target should still use label-style transfer naming: {high_level}"
);
assert!(
!high_level.contains("leave_0x0005();"),
"legacy function-style endtry placeholder should not be emitted: {high_level}"
);
}
#[test]
fn decompile_calla_with_stack_setup() {
let nef_bytes = build_nef(&[0x11, 0x10, 0x36, 0x40]);
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("calla("),
"CALLA should produce an indirect call expression: {high_level}"
);
}
#[test]
fn decompile_resolves_pusha_calla_to_internal_call_placeholder() {
let nef_bytes = build_nef(&[
0x0A, 0x0A, 0x00, 0x00, 0x00, 0x36, 0x40, 0x21, 0x21, 0x21, 0x57, 0x00, 0x00, 0x40, ]);
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("sub_0x000A()"),
"PUSHA+CALLA should resolve to the inferred method name when available: {high_level}"
);
assert!(
!high_level.contains("calla("),
"resolved PUSHA+CALLA should not remain as generic indirect call: {high_level}"
);
}
#[test]
fn decompile_resolves_local_pointer_flow_into_calla() {
let nef_bytes = build_nef(&[
0x0A, 0x09, 0x00, 0x00, 0x00, 0x70, 0x68, 0x36, 0x40, 0x57, 0x00, 0x00, 0x40, ]);
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("sub_0x0009()"),
"local pointer flow should resolve CALLA to inferred method name: {high_level}"
);
assert!(
!high_level.contains("calla(loc0)"),
"resolved local pointer flow should not remain generic CALLA: {high_level}"
);
}
#[test]
fn decompile_resolves_static_pointer_flow_into_calla() {
let nef_bytes = build_nef(&[
0x0A, 0x09, 0x00, 0x00, 0x00, 0x60, 0x58, 0x36, 0x40, 0x57, 0x00, 0x00, 0x40, ]);
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("sub_0x0009()"),
"static pointer flow should resolve CALLA to inferred method name: {high_level}"
);
assert!(
!high_level.contains("calla(static0)"),
"resolved static pointer flow should not remain generic CALLA: {high_level}"
);
}
#[test]
fn decompile_multiple_sequential_calls() {
let nef_bytes = build_nef(&[0x34, 0x02, 0x34, 0x00, 0x40, 0x40]);
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("sub_0x") || high_level.contains("call_"),
"sequential CALL instructions should each produce a call expression: {high_level}"
);
}
#[test]
fn decompile_nested_loop_in_if() {
let nef_bytes = build_nef(&[
0x10, 0x26, 0x08, 0x10, 0x26, 0x05, 0x21, 0x22, 0xFC, 0x40, ]);
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("if"),
"outer branch should produce an if block: {high_level}"
);
assert!(
high_level.contains("while"),
"inner loop should produce a while block: {high_level}"
);
assert!(
!high_level.contains("not yet translated"),
"nested loop-in-if should not emit not-yet-translated placeholders: {high_level}"
);
}
#[test]
fn decompile_try_in_loop() {
let nef_bytes = build_nef(&[
0x10, 0x26, 0x0C, 0x3B, 0x07, 0x00, 0x21, 0x3D, 0x06, 0x21, 0x3F, 0x22, 0xF5, 0x40, ]);
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("try"),
"try block should be emitted inside the loop: {high_level}"
);
assert!(
high_level.contains("while") || high_level.contains("loop"),
"enclosing loop should be recognized: {high_level}"
);
}
#[test]
fn decompile_nested_if_else() {
let nef_bytes = build_nef(&[
0x10, 0x26, 0x0A, 0x10, 0x26, 0x05, 0x21, 0x22, 0x04, 0x21, 0x21, 0x40, ]);
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("if"),
"nested if structure should be present: {high_level}"
);
assert!(
high_level.contains("let t1"),
"inner variable should still be present after empty-if removal: {high_level}"
);
}
#[test]
fn decompile_all_comparison_jumps() {
let nef_bytes = build_nef(&[
0x10, 0x11, 0x28, 0x01, 0x21, 0x10, 0x11, 0x2A, 0x01, 0x21, 0x10, 0x11, 0x2C, 0x01, 0x21, 0x10, 0x11, 0x2E, 0x01, 0x21, 0x10, 0x11, 0x30, 0x01, 0x21, 0x10, 0x11, 0x32, 0x01, 0x21, 0x40, ]);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
for op in ["==", "!=", ">", ">=", "<", "<="] {
assert!(
high_level.contains(op),
"comparison operator {op} should appear in output: {high_level}"
);
}
assert!(
!high_level.contains("not yet translated"),
"comparison jumps should not emit not-yet-translated placeholders: {high_level}"
);
}