neo-decompiler 0.10.2

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 crate::decompiler::cfg::ssa::optimize_ssa;
use crate::decompiler::cfg::structure_cfg;
use crate::decompiler::helpers::format_manifest_type;
use crate::decompiler::ir::render_block;
use crate::manifest::ContractManifest;

use super::MethodView;

/// Compose the full contract view: legacy envelope header + per-method
/// bodies + closing `}`. Used by `Decompilation::render_structured_ir`.
pub(crate) fn render_envelope(
    nef: &crate::nef::NefFile,
    manifest: Option<&ContractManifest>,
    methods: &[MethodView],
) -> String {
    use crate::decompiler::write_contract_header;
    let mut out = String::new();
    write_contract_header(&mut out, nef, manifest);
    for view in methods {
        out.push_str(&render_method_body(view, manifest));
        out.push('\n');
    }
    out.push_str("}\n");
    out
}

/// Render a method body as `fn name() -> ret { body }`. The `manifest`
/// provides the return type (looked up by method name); falls back to `void`
/// if the manifest is missing or the method is unknown.
pub(super) fn render_method_body(view: &MethodView, manifest: Option<&ContractManifest>) -> String {
    let mut ssa =
        crate::decompiler::cfg::ssa::SsaBuilder::new(&view.cfg, &view.instructions).build();
    optimize_ssa(&mut ssa);
    let block = structure_cfg(&ssa);
    let body = render_block(&block, 0);
    let ret = method_return_type(view, manifest);
    let name = sanitize_name(&view.method.name);
    if body.trim().is_empty() {
        format!("    fn {name}() -> {ret} {{\n        // empty body\n    }}\n")
    } else {
        let indented = body
            .lines()
            .map(|l| {
                if l.is_empty() {
                    String::new()
                } else {
                    format!("        {l}")
                }
            })
            .collect::<Vec<_>>()
            .join("\n");
        format!("    fn {name}() -> {ret} {{\n{indented}\n    }}\n")
    }
}

fn method_return_type(view: &MethodView, manifest: Option<&ContractManifest>) -> String {
    let Some(manifest) = manifest else {
        return "void".to_string();
    };
    manifest
        .abi
        .methods
        .iter()
        .find(|m| m.name == view.method.name)
        .map(|m| format_manifest_type(&m.return_type))
        .unwrap_or_else(|| "void".to_string())
}

fn sanitize_name(raw: &str) -> String {
    let s: String = raw
        .chars()
        .map(|c| {
            if c.is_ascii_alphanumeric() || c == '_' {
                c
            } else {
                '_'
            }
        })
        .collect();
    if s.is_empty() {
        "sub".to_string()
    } else {
        s
    }
}