use super::*;
use crate::instruction::OpCode;
#[test]
fn csharp_contract_call_lifts_to_native_method_form() {
let gas_token_hash: [u8; 20] = [
0xCF, 0x76, 0xE2, 0x8B, 0xD0, 0x06, 0x2C, 0x4A, 0x47, 0x8E, 0xE3, 0x55, 0x61, 0x01, 0x13,
0x19, 0xF3, 0xCF, 0xA4, 0xD2,
];
let mut script = vec![
OpCode::Newarray0.byte(),
OpCode::Pushint8.byte(),
0x0F,
OpCode::Pushdata1.byte(),
0x08,
];
script.extend_from_slice(b"transfer");
script.extend_from_slice(&[OpCode::Pushdata1.byte(), 0x14]);
script.extend_from_slice(&gas_token_hash);
script.extend_from_slice(&[
OpCode::Syscall.byte(),
0x62,
0x7D,
0x5B,
0x52, OpCode::Ret.byte(),
]);
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::All)
.expect("decompile succeeds");
let csharp = decompilation.csharp.as_deref().expect("csharp output");
assert!(
csharp.contains("GasToken.transfer("),
"C# output must surface the native contract call as `GasToken.transfer(args)`: {csharp}"
);
assert!(
!csharp.contains("syscall(\"System.Contract.Call\""),
"C# output must not surface the raw contract-call syscall: {csharp}"
);
}
#[test]
fn csharp_synthetic_script_entry_exposes_initslot_args_and_preserves_return() {
let nef_bytes = build_nef(&[
OpCode::Initslot.byte(),
0x00,
0x01,
OpCode::Ldarg0.byte(),
OpCode::Isnull.byte(),
OpCode::Not.byte(),
OpCode::Ret.byte(),
]);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::All)
.expect("decompile succeeds");
let csharp = decompilation.csharp.as_deref().expect("csharp output");
assert!(
csharp.contains("public static object ScriptEntry(object arg0)"),
"synthetic ScriptEntry should declare INITSLOT-counted args: {csharp}"
);
assert!(
csharp.contains("return !t0;") || csharp.contains("return !(arg0 is null);"),
"lifted return value should be preserved (not dropped via void signature): {csharp}"
);
assert!(
csharp.contains("(arg0 is null)") || csharp.contains("is_null(arg0)"),
"body should reference arg0 from the new parameter: {csharp}"
);
}
#[test]
fn csharp_omits_trailing_return_in_void_methods() {
let nef_bytes = build_nef(&[OpCode::Ret.byte()]);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::All)
.expect("decompile succeeds");
let csharp = decompilation.csharp.as_deref().expect("csharp output");
assert!(
csharp.contains("public static object ScriptEntry()"),
"synthetic ScriptEntry should default to object return when no manifest is provided: {csharp}"
);
assert!(
csharp.contains("return default;"),
"synthetic ScriptEntry with bare RET should yield `return default;`: {csharp}"
);
}
#[test]
fn csharp_keeps_explicit_return_value_in_non_void_methods() {
let nef_bytes = build_nef(&[OpCode::Push1.byte(), OpCode::Ret.byte()]);
let manifest = ContractManifest::from_json_str(
r#"
{
"name": "ReturnsInt",
"abi": {
"methods": [
{
"name": "main",
"parameters": [],
"returntype": "Integer",
"offset": 0
}
],
"events": []
},
"permissions": [],
"trusts": "*"
}
"#,
)
.expect("manifest parsed");
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, Some(manifest), OutputFormat::All)
.expect("decompile succeeds");
let csharp = decompilation.csharp.as_deref().expect("csharp output");
assert!(
csharp.contains("return 1;"),
"non-void method should keep its computed return: {csharp}"
);
}
#[test]
fn csharp_non_void_method_with_empty_body_throws_not_implemented() {
let nef_bytes = build_nef(&[
OpCode::Initslot.byte(),
0x00,
0x00,
OpCode::Push1.byte(),
OpCode::Push2.byte(),
OpCode::Add.byte(),
OpCode::Ret.byte(),
OpCode::Push16.byte(),
OpCode::Ret.byte(),
]);
let manifest = ContractManifest::from_json_str(
r#"{"name":"Demo","abi":{"methods":[
{"name":"prologue","returntype":"Integer","offset":0,"parameters":[],"safe":false},
{"name":"body","returntype":"Integer","offset":3,"parameters":[],"safe":false}
],"events":[]}}"#,
)
.expect("manifest parsed");
let decompilation = Decompiler::new()
.with_trace_comments(false)
.decompile_bytes_with_manifest(&nef_bytes, Some(manifest), OutputFormat::All)
.expect("decompile succeeds");
let csharp = decompilation.csharp.as_deref().expect("csharp output");
assert!(
csharp.contains("BigInteger prologue()"),
"prologue should render with its declared return type: {csharp}"
);
assert!(
csharp.contains("throw new NotImplementedException();"),
"non-void method with an empty lifted body must throw, not emit a bare comment: {csharp}"
);
}