use super::*;
#[test]
fn csharp_synthetic_script_entry_exposes_initslot_args_and_preserves_return() {
let nef_bytes = build_nef(&[0x57, 0x00, 0x01, 0x78, 0xD8, 0xAA, 0x40]);
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(&[0x40]);
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(&[0x11, 0x40]);
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_translates_loop_to_while_true() {
let script = [
0x57, 0x01, 0x00, 0x10, 0x70, 0x68, 0x13, 0xB5, 0x26, 0x07, 0x21, 0x68, 0x11, 0x9E, 0x70,
0x22, 0xF4, 0x40,
];
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("while (true) {"),
"C# output should translate `loop {{` to `while (true) {{`: {csharp}"
);
assert!(
!csharp.contains("loop {"),
"C# output should not retain the high-level `loop` keyword: {csharp}"
);
}
#[test]
fn csharp_translates_switch_to_idiomatic_c_sharp() {
let script = [
0x57, 0x01, 0x00, 0x11, 0x70, 0x68, 0x10, 0x97, 0x26, 0x06, 0x1A, 0x70, 0x22, 0x0D, 0x68,
0x11, 0x97, 0x26, 0x06, 0x1B, 0x70, 0x22, 0x04, 0x1C, 0x70, 0x68, 0x40,
];
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("switch (loc0) {"),
"switch scrutinee should be parenthesised: {csharp}"
);
assert!(
csharp.contains("case 0: {"),
"case label should use C# `: {{` form: {csharp}"
);
assert!(
csharp.contains("case 1: {"),
"second case should also use `: {{`: {csharp}"
);
assert!(
csharp.contains("default: {"),
"default label should use `default: {{`: {csharp}"
);
let break_count = csharp.matches("break;").count();
assert!(
break_count >= 3,
"each case (including default) should end with `break;` (found {break_count}): {csharp}"
);
assert!(
!csharp.contains("case 0 {"),
"C# output should not retain the high-level `case X {{` form: {csharp}"
);
assert!(
!csharp.contains("default {"),
"C# output should not retain the high-level `default {{` form: {csharp}"
);
}
#[test]
fn csharp_else_if_chain_uses_parenthesised_conditions() {
let script = [
0x57, 0x01, 0x00, 0x11, 0x70, 0x68, 0x10, 0x97, 0x26, 0x06, 0x1A, 0x70, 0x22, 0x0D, 0x68,
0x11, 0x97, 0x26, 0x06, 0x1B, 0x70, 0x22, 0x04, 0x1C, 0x70, 0x68, 0x40,
];
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");
for line in csharp.lines() {
let trimmed = line.trim();
assert!(
!trimmed.starts_with("else if ") || trimmed.starts_with("else if ("),
"C# else-if must use parenthesised condition: {trimmed}"
);
assert!(
!trimmed.starts_with("} else if ") || trimmed.starts_with("} else if ("),
"C# `}} else if` must use parenthesised condition: {trimmed}"
);
}
}
#[test]
fn csharp_view_respects_manifest_metadata_and_parameters() {
let nef_bytes = sample_nef();
let manifest = ContractManifest::from_json_str(
r#"
{
"name": "Demo",
"abi": {
"methods": [
{
"name": "deploy-contract",
"parameters": [
{"name": "owner-name", "type": "Hash160"},
{"name": "amount", "type": "Integer"}
],
"returntype": "Void",
"offset": 0
}
],
"events": []
},
"permissions": [],
"trusts": "*",
"extra": {"Author": "Jane Doe", "Email": "jane@example.com"}
}
"#,
)
.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("[ManifestExtra(\"Author\", \"Jane Doe\")]"));
assert!(csharp.contains("[ManifestExtra(\"Email\", \"jane@example.com\")]"));
assert!(csharp
.contains("public static void deploy_contract(UInt160 owner_name, BigInteger amount)"));
}
#[test]
fn high_level_view_renders_manifest_groups_block() {
let nef_bytes = sample_nef();
let manifest = ContractManifest::from_json_str(
r#"
{
"name": "Demo",
"groups": [
{"pubkey": "02f49ce0c33aabbccdd", "signature": "BAt..."},
{"pubkey": "02b00b1eaaaabbbbcccc", "signature": "BAd..."}
],
"abi": {"methods": [], "events": []},
"permissions": [],
"trusts": "*",
"extra": {}
}
"#,
)
.expect("manifest parsed");
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, Some(manifest), OutputFormat::All)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(
high_level.contains("groups {"),
"high-level should open a groups block:\n{high_level}"
);
assert!(high_level.contains("pubkey=02f49ce0c33aabbccdd"));
assert!(high_level.contains("pubkey=02b00b1eaaaabbbbcccc"));
assert!(!high_level.contains("BAt..."));
assert!(!high_level.contains("signature="));
let csharp = decompilation.csharp.as_deref().expect("csharp output");
assert!(
csharp.contains("// groups:"),
"C# header should open a groups comment block:\n{csharp}"
);
assert!(csharp.contains("// pubkey=02f49ce0c33aabbccdd"));
assert!(csharp.contains("// pubkey=02b00b1eaaaabbbbcccc"));
assert!(!csharp.contains("BAt..."));
assert!(!csharp.contains("signature="));
}
#[test]
fn csharp_view_renders_non_string_scalar_extra_metadata() {
let nef_bytes = sample_nef();
let manifest = ContractManifest::from_json_str(
r#"
{
"name": "Demo",
"abi": {"methods": [], "events": []},
"permissions": [],
"trusts": "*",
"extra": {
"Author": "Anon",
"Version": 2,
"Verified": true,
"Notes": null
}
}
"#,
)
.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("[ManifestExtra(\"Author\", \"Anon\")]"));
assert!(csharp.contains("[ManifestExtra(\"Version\", \"2\")]"));
assert!(csharp.contains("[ManifestExtra(\"Verified\", \"true\")]"));
assert!(!csharp.contains("ManifestExtra(\"Notes\""));
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(high_level.contains("// Author: Anon"));
assert!(high_level.contains("// Version: 2"));
assert!(high_level.contains("// Verified: true"));
assert!(!high_level.contains("// Notes:"));
}
#[test]
fn csharpize_statement_converts_known_forms() {
assert_eq!(csharpize_statement(" "), "");
assert_eq!(csharpize_statement("// note"), "// note");
assert_eq!(csharpize_statement("let x = 1;"), "var x = 1;");
assert_eq!(
csharpize_statement("let t0 = min(x, y);"),
"var t0 = BigInteger.Min(x, y);"
);
assert_eq!(
csharpize_statement("let t0 = is_null(loc0);"),
"var t0 = (loc0 is null);"
);
assert_eq!(csharpize_statement("let t0 = a cat b;"), "var t0 = a + b;");
assert_eq!(
csharpize_statement("throw(min(a, b));"),
"throw new Exception($\"{BigInteger.Min(a, b)}\");"
);
assert_eq!(
csharpize_statement("abort(\"err\" cat code);"),
"throw new Exception(\"err\" + code);"
);
assert_eq!(
csharpize_statement("assert(is_null(loc0));"),
"if (!((loc0 is null))) throw new Exception();"
);
assert_eq!(
csharpize_statement("assert(min(a, b) > 0, \"e\" cat code);"),
"if (!(BigInteger.Min(a, b) > 0)) throw new Exception(\"e\" + code);"
);
assert_eq!(
csharpize_statement("assert(x > 0, code);"),
"if (!(x > 0)) throw new Exception($\"{code}\");"
);
assert_eq!(csharpize_statement("if t0 {"), "if (t0) {");
assert_eq!(csharpize_statement("while t1 {"), "while (t1) {");
assert_eq!(csharpize_statement("loop {"), "while (true) {");
assert_eq!(
csharpize_statement("else if loc0 < 3 {"),
"else if (loc0 < 3) {"
);
assert_eq!(
csharpize_statement("} else if loc0 == 1 {"),
"} else if (loc0 == 1) {"
);
assert_eq!(
csharpize_statement("for (let i = 0; i < 3; i++) {"),
"for (var i = 0; i < 3; i++) {"
);
assert_eq!(
csharpize_statement("leave label_0x0010;"),
"goto label_0x0010;"
);
assert_eq!(
csharpize_statement("return \"b:\" cat addr;"),
"return \"b:\" + addr;"
);
assert_eq!(
csharpize_statement("var x = a cat b cat c;"),
"var x = a + b + c;"
);
assert_eq!(
csharpize_statement("var msg = \"says cat ok\";"),
"var msg = \"says cat ok\";"
);
assert_eq!(
csharpize_statement("throw(\"oops\");"),
"throw new Exception(\"oops\");"
);
assert_eq!(
csharpize_statement("throw(error_msg);"),
"throw new Exception($\"{error_msg}\");"
);
assert_eq!(csharpize_statement("abort();"), "throw new Exception();");
assert_eq!(
csharpize_statement("abort(\"fatal\");"),
"throw new Exception(\"fatal\");"
);
assert_eq!(
csharpize_statement("abort(reason);"),
"throw new Exception($\"{reason}\");"
);
assert_eq!(
csharpize_statement("assert(x > 0);"),
"if (!(x > 0)) throw new Exception();"
);
assert_eq!(
csharpize_statement("assert(x > 0, \"must be positive\");"),
"if (!(x > 0)) throw new Exception(\"must be positive\");"
);
assert_eq!(
csharpize_statement("assert(foo(a, b));"),
"if (!(foo(a, b))) throw new Exception();"
);
assert_eq!(
csharpize_statement("var x = abs(loc0);"),
"var x = BigInteger.Abs(loc0);"
);
assert_eq!(
csharpize_statement("var x = min(a, b);"),
"var x = BigInteger.Min(a, b);"
);
assert_eq!(
csharpize_statement("var x = max(a, b);"),
"var x = BigInteger.Max(a, b);"
);
assert_eq!(
csharpize_statement("var x = pow(base, exp);"),
"var x = BigInteger.Pow(base, (int)(exp));"
);
assert_eq!(
csharpize_statement("var x = pow(2, 8);"),
"var x = BigInteger.Pow(2, 8);"
);
assert_eq!(
csharpize_statement("var x = left(buf, 4);"),
"var x = Helper.Left(buf, 4);"
);
assert_eq!(
csharpize_statement("var x = substr(buf, 0, 16);"),
"var x = Helper.Substr(buf, 0, 16);"
);
assert_eq!(
csharpize_statement("var x = mypow(2);"),
"var x = mypow(2);"
);
assert_eq!(
csharpize_statement("var x = \"min(a, b)\";"),
"var x = \"min(a, b)\";"
);
assert_eq!(
csharpize_statement("var x = max(abs(a), b);"),
"var x = BigInteger.Max(BigInteger.Abs(a), b);"
);
assert_eq!(
csharpize_statement("var x = sign(loc0);"),
"var x = Helper.Sign(loc0);"
);
assert_eq!(
csharpize_statement("var x = sqrt(loc0);"),
"var x = Helper.Sqrt(loc0);"
);
assert_eq!(
csharpize_statement("var x = modmul(a, b, m);"),
"var x = Helper.ModMul(a, b, m);"
);
assert_eq!(
csharpize_statement("var x = modpow(b, e, m);"),
"var x = BigInteger.ModPow(b, e, m);"
);
assert_eq!(
csharpize_statement("var x = within(v, lo, hi);"),
"var x = Helper.Within(v, lo, hi);"
);
assert_eq!(
csharpize_statement("var x = left(buf, n);"),
"var x = Helper.Left(buf, (int)(n));"
);
assert_eq!(
csharpize_statement("var x = right(buf, n);"),
"var x = Helper.Right(buf, (int)(n));"
);
assert_eq!(
csharpize_statement("var x = substr(buf, start, len);"),
"var x = Helper.Substr(buf, (int)(start), (int)(len));"
);
assert_eq!(
csharpize_statement("if is_null(loc0) {"),
"if ((loc0 is null)) {"
);
assert_eq!(
csharpize_statement("var x = is_null(loc0);"),
"var x = (loc0 is null);"
);
assert_eq!(
csharpize_statement("var y = !is_null(loc0);"),
"var y = !(loc0 is null);"
);
assert_eq!(
csharpize_statement("var x = my_is_null(loc0);"),
"var x = my_is_null(loc0);"
);
assert_eq!(
csharpize_statement("var t0 = Map();"),
"var t0 = new Map<object, object>();"
);
assert_eq!(
csharpize_statement("var t0 = [];"),
"var t0 = new object[0];"
);
assert_eq!(
csharpize_statement("var t0 = Struct();"),
"var t0 = new Struct();"
);
assert_eq!(
csharpize_statement("var t0 = MyMap();"),
"var t0 = MyMap();"
);
assert_eq!(
csharpize_statement("var t0 = \"Map()\";"),
"var t0 = \"Map()\";"
);
assert_eq!(
csharpize_statement("var t0 = new_buffer(8);"),
"var t0 = new byte[8];"
);
assert_eq!(
csharpize_statement("var t0 = new_buffer(loc0);"),
"var t0 = new byte[(int)(loc0)];"
);
assert_eq!(
csharpize_statement("var t0 = new_array(3);"),
"var t0 = new object[3];"
);
assert_eq!(
csharpize_statement("var t0 = new_array(-3);"),
"var t0 = new object[-3];"
);
assert_eq!(
csharpize_statement("var t0 = my_new_buffer(8);"),
"var t0 = my_new_buffer(8);"
);
assert_eq!(
csharpize_statement("var t0 = convert_to_bool(loc0);"),
"var t0 = (bool)(loc0);"
);
assert_eq!(
csharpize_statement("var t0 = convert_to_integer(loc0);"),
"var t0 = (BigInteger)(loc0);"
);
assert_eq!(
csharpize_statement("var t0 = convert_to_bytestring(loc0);"),
"var t0 = (ByteString)(loc0);"
);
assert_eq!(
csharpize_statement("var t0 = convert_to_buffer(loc0);"),
"var t0 = (byte[])(loc0);"
);
assert_eq!(
csharpize_statement("if is_type_bool(loc0) {"),
"if ((loc0 is bool)) {"
);
assert_eq!(
csharpize_statement("var t0 = is_type_integer(loc0);"),
"var t0 = (loc0 is BigInteger);"
);
assert_eq!(
csharpize_statement("var t0 = is_type_bytestring(loc0);"),
"var t0 = (loc0 is ByteString);"
);
assert_eq!(
csharpize_statement("var t0 = is_type_buffer(loc0);"),
"var t0 = (loc0 is byte[]);"
);
assert_eq!(
csharpize_statement("var t0 = convert_to_array(loc0);"),
"var t0 = convert_to_array(loc0);"
);
assert_eq!(
csharpize_statement("var t0 = is_type_map(loc0);"),
"var t0 = is_type_map(loc0);"
);
assert_eq!(csharpize_statement("clear_items(loc0);"), "loc0.Clear();");
assert_eq!(
csharpize_statement("remove_item(loc0, key);"),
"loc0.Remove(key);"
);
assert_eq!(
csharpize_statement("var t0 = keys(loc0);"),
"var t0 = loc0.Keys;"
);
assert_eq!(
csharpize_statement("var t0 = values(loc0);"),
"var t0 = loc0.Values;"
);
assert_eq!(
csharpize_statement("reverse_items(loc0);"),
"loc0.Reverse();"
);
assert_eq!(
csharpize_statement("var t0 = my_keys(loc0);"),
"var t0 = my_keys(loc0);"
);
assert_eq!(csharpize_statement("append(loc0, 42);"), "loc0.Add(42);");
assert_eq!(
csharpize_statement("var t0 = has_key(loc0, key);"),
"var t0 = loc0.ContainsKey(key);"
);
assert_eq!(
csharpize_statement("if has_key(loc0, key) {"),
"if (loc0.ContainsKey(key)) {"
);
}
#[test]
fn csharp_resolves_internal_calls_to_method_names() {
let script = [0x34, 0x04, 0x40, 0x21, 0x40];
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("sub_0x0004()"),
"C# output should resolve internal helper names instead of raw call placeholders: {csharp}"
);
assert!(
!csharp.contains("call_0x0004"),
"C# output should not emit raw call_0x placeholders when a helper name is known: {csharp}"
);
}
#[test]
fn csharp_emits_inferred_helper_methods() {
let script = [0x34, 0x04, 0x40, 0x21, 0x40];
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("private static dynamic sub_0x0004()")
|| csharp.contains("private static void sub_0x0004()")
|| csharp.contains("private static BigInteger sub_0x0004()")
|| csharp.contains("private static object sub_0x0004()"),
"C# output should emit inferred helper method definitions for resolved internal calls: {csharp}"
);
assert!(
!csharp.contains("sub_0x0003"),
"C# output should not emit nop-only inferred helper methods: {csharp}"
);
}
#[test]
fn csharp_inferred_nonvoid_helpers_do_not_emit_bare_return() {
let script = [0x34, 0x04, 0x40, 0x21, 0x40];
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(
"private static dynamic sub_0x0004()
{
// 0004: RET
return;"
),
"non-void inferred helper bodies should not emit bare return statements: {csharp}"
);
}
#[test]
fn csharp_includes_offsetless_manifest_methods_as_stubs() {
let nef_bytes = sample_nef();
let manifest = ContractManifest::from_json_str(
r#"
{
"name": "Stubby",
"abi": {
"methods": [
{ "name": "main", "parameters": [], "returntype": "Void", "offset": 0 },
{ "name": "helper", "parameters": [], "returntype": "Void" }
],
"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("public static void helper()"),
"offsetless method should appear in C# skeleton"
);
assert!(
csharp.contains("NotImplementedException"),
"offsetless method should be emitted as a stub"
);
}
#[test]
fn csharp_includes_manifest_events() {
let nef_bytes = sample_nef();
let manifest = ContractManifest::from_json_str(
r#"
{
"name": "Events",
"abi": {
"methods": [
{ "name": "main", "parameters": [], "returntype": "Void", "offset": 0 }
],
"events": [
{
"name": "transfer-event",
"parameters": [
{ "name": "from", "type": "Hash160" },
{ "name": "to", "type": "Hash160" },
{ "name": "amount", "type": "Integer" }
]
}
]
},
"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("[DisplayName(\"transfer-event\")]"));
assert!(
csharp.contains("public static event Action<UInt160, UInt160, BigInteger> transfer_event;")
);
}
#[test]
fn csharp_escapes_reserved_keywords() {
let nef_bytes = sample_nef();
let manifest = ContractManifest::from_json_str(
r#"
{
"name": "class",
"abi": {
"methods": [
{
"name": "class",
"parameters": [{ "name": "namespace", "type": "Integer" }],
"returntype": "Void",
"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("public class @class : SmartContract"));
assert!(csharp.contains("public static void @class(BigInteger @namespace)"));
}
#[test]
fn csharp_uses_label_style_for_transfer_placeholders() {
let nef_bytes = build_nef(&[0x3D, 0x06, 0x11, 0x12, 0x9E, 0x75, 0x40]);
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("goto label_0x0006;"),
"C# should normalize leave-transfers to goto label style: {csharp}"
);
assert!(
csharp.contains("label_0x0006:"),
"C# should emit label declaration for transfer targets: {csharp}"
);
assert!(
!csharp.contains("leave label_"),
"C# should not emit non-C# leave statements: {csharp}"
);
assert!(
!csharp.contains("leave_0x"),
"C# should not emit legacy function-style transfer placeholders: {csharp}"
);
}
#[test]
fn csharp_mismatch_offset_emits_script_entry_and_manifest_method() {
let nef_bytes = build_nef(&[0x11, 0x40, 0x12, 0x40]);
let manifest = ContractManifest::from_json_str(
r#"
{
"name": "OffsetMismatch",
"abi": {
"methods": [
{
"name": "helper",
"parameters": [],
"returntype": "Integer",
"offset": 2
}
],
"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("public static object ScriptEntry()"),
"C# output should keep a synthetic script-entry method when ABI offsets do not include bytecode entry"
);
assert!(
csharp.contains("public static BigInteger helper()"),
"C# output should still emit the manifest method"
);
let before_helper = csharp
.split("public static BigInteger helper")
.next()
.expect("entry section present");
assert!(
before_helper.contains("// 0000: PUSH1"),
"script-entry body should contain bytecode from script start"
);
assert!(
!before_helper.contains("// 0002: PUSH2"),
"script-entry body should stop before helper method offset"
);
}
#[test]
fn csharp_missing_manifest_offset_uses_first_method_as_entry_signature() {
let nef_bytes = sample_nef();
let manifest = ContractManifest::from_json_str(
r#"
{
"name": "OffsetMissing",
"abi": {
"methods": [
{
"name": "main",
"parameters": [],
"returntype": "Integer"
}
],
"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("public static BigInteger main()"),
"C# output should reuse the first manifest method signature when offsets are missing"
);
assert!(
!csharp.contains("public static void ScriptEntry()"),
"synthetic ScriptEntry should not be emitted when the manifest omits entry offsets entirely"
);
assert!(
!csharp.contains("NotImplementedException"),
"the fallback entry method should not also be emitted as an offset-less stub"
);
}
#[test]
fn csharp_trims_initslot_boundaries() {
let Some(nef_bytes) = try_load_testing_nef("Contract_Delegate.nef") else {
eprintln!("Skipping: Contract_Delegate.nef not found in devpack artifacts");
return;
};
let Some(manifest) = try_load_testing_manifest("Contract_Delegate.manifest.json") else {
eprintln!("Skipping: Contract_Delegate.manifest.json not found");
return;
};
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");
let sum_block = csharp
.split("public static BigInteger sumFunc")
.nth(1)
.and_then(|rest| rest.split("private static dynamic sub_0x000C").next())
.expect("sumFunc block present");
assert!(
sum_block.contains("// 0000: INITSLOT"),
"sumFunc should still show its entry INITSLOT"
);
assert!(
!sum_block.contains("// 000C: INITSLOT"),
"sumFunc body should stop before the inferred helper block"
);
assert!(
!sum_block.contains("return t23;"),
"duplicate return from appended block should not appear in sumFunc"
);
assert!(
csharp.contains("private static dynamic sub_0x000C"),
"inferred helper should now be emitted separately"
);
}
#[test]
fn csharp_multi_entry_typed_trusts_render_as_block() {
let nef_bytes = build_nef(&[0x40]);
let manifest = ContractManifest::from_json_str(
r#"
{
"name": "MultiTrust",
"abi": {
"methods": [
{
"name": "main",
"parameters": [],
"returntype": "Void",
"offset": 0
}
],
"events": []
},
"permissions": [],
"trusts": {
"hashes": ["0xabc", "0xdef"],
"groups": ["02foo", "02bar"]
}
}
"#,
)
.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("// trusts:"),
"multi-entry typed trusts should render as a block: {csharp}"
);
assert!(
csharp.contains("// hash:0xabc"),
"hash entries should be indented under the trusts block: {csharp}"
);
assert!(
csharp.contains("// hash:0xdef"),
"hash entries should be indented under the trusts block: {csharp}"
);
assert!(
csharp.contains("// group:02foo"),
"group entries should be indented under the trusts block: {csharp}"
);
assert!(
csharp.contains("// group:02bar"),
"group entries should be indented under the trusts block: {csharp}"
);
assert!(
!csharp.contains("// trusts = [hash:0xabc, hash:0xdef, group:02foo, group:02bar]"),
"multi-entry trusts must not stretch onto a single line: {csharp}"
);
}
#[test]
fn header_surfaces_nef_compiler_and_source_fields() {
let nef_bytes = sample_nef();
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::All)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(
high_level.contains(" // compiler: test"),
"high-level should surface the NEF compiler field:\n{high_level}"
);
assert!(
!high_level.contains(" // source:"),
"empty source should not emit a placeholder line:\n{high_level}"
);
let csharp = decompilation.csharp.as_deref().expect("csharp output");
assert!(
csharp.contains(" // compiler: test"),
"C# header should surface the NEF compiler field at the C# indent:\n{csharp}"
);
assert!(
!csharp.contains("// source:"),
"empty source should not emit a placeholder line in C#:\n{csharp}"
);
}
#[test]
fn csharp_header_renders_method_tokens_block() {
let stdlib_hash: [u8; 20] = [
0xC0, 0xEF, 0x39, 0xCE, 0xE0, 0xE4, 0xE9, 0x25, 0xC6, 0xC2, 0xA0, 0x6A, 0x79, 0xE1, 0x44,
0x0D, 0xD8, 0x6F, 0xCE, 0xAC,
];
let nef_bytes = build_nef_with_single_token(&[0x40], stdlib_hash, "Serialize", 1, true, 0x0F);
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("// method tokens declared in NEF:"),
"C# header should open the method tokens comment block:\n{csharp}"
);
assert!(
csharp.contains(
"// Serialize (StdLib::Serialize) hash=C0EF39CEE0E4E925C6C2A06A79E1440DD86FCEAC params=1 returns=true flags=0x0F"
),
"method token line should match high-level layout (with native contract label):\n{csharp}"
);
assert!(
csharp.contains("(ReadStates|WriteStates|AllowCall|AllowNotify)"),
"call flags should be described:\n{csharp}"
);
}
#[test]
fn csharp_header_omits_method_tokens_block_when_none() {
let nef_bytes = sample_nef();
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("method tokens declared in NEF"),
"no token block expected when NEF has no method tokens:\n{csharp}"
);
}
#[test]
fn csharp_single_entry_typed_trusts_stay_on_one_line() {
let nef_bytes = build_nef(&[0x40]);
let manifest = ContractManifest::from_json_str(
r#"
{
"name": "SingleTrust",
"abi": {
"methods": [
{
"name": "main",
"parameters": [],
"returntype": "Void",
"offset": 0
}
],
"events": []
},
"permissions": [],
"trusts": { "groups": ["02abcdef"] }
}
"#,
)
.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("// trusts = [group:02abcdef]"),
"single-entry trusts should stay compact on one line: {csharp}"
);
assert!(
!csharp.contains("// trusts:"),
"single-entry trusts should not break into a block: {csharp}"
);
}