neo-decompiler 0.11.0

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 std::collections::HashSet;

use serde::Serialize;
mod instructions;
mod manifest;
mod method_tokens;

use crate::decompiler::analysis::{call_graph::CallGraph, types::TypeInfo, xrefs::Xrefs};
use crate::decompiler::Decompilation;
use crate::disassembler::DisassemblyOutput;
use crate::manifest::ContractManifest;
use crate::nef::NefFile;
use crate::util;

use instructions::InstructionReport;
use manifest::{summarize_manifest, ManifestSummary};
use method_tokens::{build_method_token_report, collect_warnings, MethodTokenReport};

/// Browser-friendly summary of `info` output.
#[derive(Debug, Clone, Serialize)]
pub struct WebInfoReport {
    compiler: String,
    source: Option<String>,
    script_length: usize,
    script_hash_le: String,
    script_hash_be: String,
    checksum: String,
    method_tokens: Vec<MethodTokenReport>,
    manifest: Option<ManifestSummary>,
    warnings: Vec<String>,
}

/// Browser-friendly summary of `disasm` output.
#[derive(Debug, Clone, Serialize)]
pub struct WebDisasmReport {
    script_hash_le: String,
    script_hash_be: String,
    instructions: Vec<InstructionReport>,
    warnings: Vec<String>,
}

/// Browser-friendly summary of `decompile` output.
#[derive(Debug, Clone, Serialize)]
pub struct WebDecompileReport {
    compiler: String,
    source: Option<String>,
    script_hash_le: String,
    script_hash_be: String,
    csharp: String,
    high_level: String,
    pseudocode: String,
    instructions: Vec<InstructionReport>,
    method_tokens: Vec<MethodTokenReport>,
    manifest: Option<ManifestSummary>,
    analysis: AnalysisReport,
    warnings: Vec<String>,
}

pub(super) fn build_info_report(
    nef: &NefFile,
    manifest: Option<&ContractManifest>,
    parse_warnings: &[String],
) -> WebInfoReport {
    let script_hash = nef.script_hash();
    let method_tokens: Vec<MethodTokenReport> = nef
        .method_tokens
        .iter()
        .map(build_method_token_report)
        .collect();
    let mut warnings = parse_warnings.to_vec();
    for warning in collect_warnings(&method_tokens) {
        if !warnings.contains(&warning) {
            warnings.push(warning);
        }
    }
    WebInfoReport {
        compiler: nef.header.compiler.trim_end_matches('\0').to_string(),
        source: (!nef.header.source.is_empty()).then(|| nef.header.source.clone()),
        script_length: nef.script.len(),
        script_hash_le: util::format_hash(&script_hash),
        script_hash_be: util::format_hash_be(&script_hash),
        checksum: format!("0x{:08X}", nef.checksum),
        warnings,
        method_tokens,
        manifest: manifest.map(summarize_manifest),
    }
}

pub(super) fn build_disasm_report(
    nef: &NefFile,
    output: DisassemblyOutput,
    parse_warnings: &[String],
) -> WebDisasmReport {
    let script_hash = nef.script_hash();
    let mut warnings = parse_warnings.to_vec();
    for warning in output.warnings.iter().map(ToString::to_string) {
        if !warnings.contains(&warning) {
            warnings.push(warning);
        }
    }
    WebDisasmReport {
        script_hash_le: util::format_hash(&script_hash),
        script_hash_be: util::format_hash_be(&script_hash),
        instructions: output
            .instructions
            .iter()
            .map(InstructionReport::from)
            .collect(),
        warnings,
    }
}

pub(super) fn build_decompile_report(result: Decompilation) -> WebDecompileReport {
    let Decompilation {
        nef,
        manifest,
        warnings: decompile_warnings,
        instructions,
        call_graph,
        xrefs,
        types,
        pseudocode,
        high_level,
        csharp,
        ..
    } = result;
    let script_hash = nef.script_hash();
    let method_tokens: Vec<MethodTokenReport> = nef
        .method_tokens
        .iter()
        .map(build_method_token_report)
        .collect();
    let mut warnings = Vec::new();
    let mut seen = HashSet::new();
    for warning in collect_warnings(&method_tokens)
        .into_iter()
        .chain(decompile_warnings)
    {
        if seen.insert(warning.clone()) {
            warnings.push(warning);
        }
    }

    WebDecompileReport {
        compiler: nef.header.compiler.trim_end_matches('\0').to_string(),
        source: (!nef.header.source.is_empty()).then(|| nef.header.source.clone()),
        script_hash_le: util::format_hash(&script_hash),
        script_hash_be: util::format_hash_be(&script_hash),
        csharp: csharp.unwrap_or_default(),
        high_level: high_level.unwrap_or_default(),
        pseudocode: pseudocode.unwrap_or_default(),
        instructions: instructions.iter().map(InstructionReport::from).collect(),
        method_tokens,
        manifest: manifest.as_ref().map(summarize_manifest),
        analysis: AnalysisReport {
            call_graph,
            xrefs,
            types,
        },
        warnings,
    }
}

#[derive(Debug, Clone, Serialize)]
struct AnalysisReport {
    call_graph: CallGraph,
    xrefs: Xrefs,
    types: TypeInfo,
}