Neo N3 NEF Inspector
This project provides a small, well-tested Rust crate and CLI for inspecting Neo N3 NEF bytecode packages. It focuses on the essential pieces that are easy to run locally: parsing the NEF container (header, method tokens, checksum), loading the companion contract manifest, decoding a useful slice of Neo VM opcodes, and rendering both pseudocode and a high-level contract skeleton.
What you get
- NEF header parsing (magic, compiler, version, script length, checksum)
- Script hash calculation (Hash160) exposed in both little-endian and canonical forms
- Method token decoding using the official variable-length encoding
- Opcode metadata generated from the upstream Neo VM source (unknown mnemonics
fall back to informative comments, and new opcodes can be added via
tools/generate_opcodes.py) - Manifest parsing (
.manifest.json) with ABI, feature, group, permission, and trust details that surface in both text and JSON outputs - Disassembly for common opcodes such as
PUSH*, arithmetic operations, jumps, calls, andSYSCALL(tolerant by default; optional fail-fast flag for unknown opcodes) - Syscall metadata resolution with human-readable names, call flags, and return arity (void syscalls avoid phantom temporaries in the high-level view)
- Native contract lookup so method tokens can be paired with contract names
- High-level contract view that surfaces manifest ABI data, names locals/args
via slot instructions (including manifest parameter names), and lifts stack
operations into readable statements with structured
if/else,for,while, anddo { } whileblocks plus emittedbreak/continuestatements and manifest-derived entry signatures - Syscall lifting that resolves human-readable names and suppresses phantom temporaries for known void syscalls (e.g., Runtime.Notify, Storage.Put)
- A simple pseudocode view mirroring the decoded instruction stream
- A C# contract skeleton view (
--format csharp) that mirrors the manifest entry point and emits stubs for additional ABI methods - A single binary (
neo-decompiler) and a reusable library (neo_decompiler)
Quick start
# Build the binary
# Print header information
# Emit machine-readable header information (includes checksum, script hash, ABI, tokens, manifest path)
# Decode instructions
# Fail fast on unknown opcodes (default is tolerant)
# Machine-readable disassembly (tolerant by default)
# Emit the high-level contract view (auto-detects `*.manifest.json` if present)
# Fail fast on unknown opcodes during high-level reconstruction
# Emit the legacy pseudocode listing
# Emit a C# contract skeleton (includes manifest extras like Author/Email when present)
# Machine-readable decompilation (high-level, pseudocode, manifest path, metadata)
# Inspect method tokens
# Machine-readable tokens listing
# Use --json-compact alongside any JSON format to minimise whitespace
Permissions example
Given a manifest snippet:
The info command prints:
Permissions:
- contract=hash:0x0123... methods=["symbol"]
- contract=group:03ABCD... methods=*
Trusts: *
Corresponding JSON (truncated) mirrors the schema:
"manifest":
Worked example (nccs)
The repository ships with a minimal C# contract under
examples/hello_world. You can compile it
with the official Neo C# compiler (nccs) and immediately feed the result into
the decompiler:
# Install the Neo compiler if you do not already have it
# Compile the example contract
# Decompile (auto-detects the manifest sitting next to the NEF)
The examples/README.md file explains the walkthrough and can serve as a
starting point for your own experiments.
Installation
From crates.io (recommended)
From GitHub releases
Download pre-built binaries from the releases page.
From source
# Install the latest release from git
# Or install the latest development version
# Or install locally from a checkout
Library example
use Decompiler;
contract.type is Hash for explicit script hashes, Group for public-key groups,
and Wildcard when * is specified. methods.type mirrors the same wildcard vs list
semantics (e.g., Methods with value: ["symbol"]).
Built-in metadata coverage
All published Neo N3 opcodes, syscalls, and native contracts are bundled with the crate so there is no network or tooling dependency at runtime:
src/opcodes_generated.rsis produced bytools/generate_opcodes.py, which scrapes the upstreamOpCode.csfile and emits every mnemonic alongside its byte value and operand encoding.src/syscalls_generated.rsoriginates fromtools/data/syscalls.jsonand lists each syscall hash, its friendly name, handler, price, and call-flag mask.crate::syscalls::lookupwires this table into the disassembler and high-level view so everySYSCALLshows human-readable context.src/native_contracts_generated.rsis generated fromtools/data/native_contracts.jsonand enumerates every native contract hash plus its publicly-exposed methods, ensuring method tokens are annotated with canonical names when possible.
Re-run the scripts in tools/ whenever Neo introduces new entries. Each script
overwrites the corresponding generated Rust file, so git status immediately
highlights the delta and the expanded coverage is propagated to the CLI and
library APIs.
Use the CLI to browse these tables directly:
# List all syscalls with hashes, handlers, and call flags
neo-decompiler catalog syscalls
# Machine-readable native-contract catalog
neo-decompiler catalog native-contracts --format json
# Enumerate every opcode and operand encoding
neo-decompiler catalog opcodes
Testing artifacts
- Drop real contracts anywhere under
TestingArtifacts/to extend coverage:- C# source with embedded manifest/NEF blobs (
*.cs) are parsed and rewritten intoTestingArtifacts/decompiled/<relative>/. - Paired files (
Example.nef+Example.manifest.json) are also picked up automatically (recursively).
- C# source with embedded manifest/NEF blobs (
- Known limitations can be listed in
TestingArtifacts/known_unsupported.txt(one name per line,#for comments, optionalpath:expected substringto assert the error text); matching artifacts are allowed to fail and are copied to*.error.txt. - Outputs mirror the artifact layout under
TestingArtifacts/decompiled/, which is git-ignored by default. Known-unsupported entries are still processed and must emit a non-empty*.error.txtto document the failure reason. - Current samples ship under
TestingArtifacts/edgecases/(loop lifting, method tokens, manifest metadata, permissions/trusts, call-flag failure, events) andTestingArtifacts/embedded/(compiler-style C# with embedded manifest/NEF).
Extending opcode coverage
The disassembler prints informative comments for opcodes that are not yet translated
(// XXXX: <MNEMONIC> (not yet translated)). To extend support, update
tools/generate_opcodes.py (which regenerates src/opcodes_generated.rs) and add
handling in src/decompiler.rs/src/cli.rs for any new instructions.
Scope and limitations
- NEF checksums are verified using the same double-SHA256 calculation employed by the official toolchain. Files with mismatching checksums are rejected.
- The disassembler covers the opcodes exercised by our tests (including the
various
PUSH*forms, short/long jumps, calls, andSYSCALL). Unrecognised opcodes still produce informative comments so you can decide how to extend the decoder. - The high-level contract view (and the derived C# skeleton) performs
lightweight stack lifting (constants, arithmetic, simple returns, syscalls)
and recognises structured control flow such as
if/else,for,while, anddo { } whileloops (includingbreak/continuebranches). Complex reconstruction such as full control-flow graphs or type inference is intentionally out of scope.
Troubleshooting
- "manifest not provided" in JSON/text output – ensure the
.manifest.jsonfile sits next to the NEF or pass it explicitly via--manifest path/to/file. - Manifest path missing in text/JSON output – both views show the detected
path (look for
Manifest path:in text, ormanifest_pathin JSON). If it is absent/null, pass--manifest path/to/contract.manifest.jsonexplicitly. - Checksum mismatch errors – the CLI re-computes the NEF hash; re-build the contract to regenerate the NEF or verify you are pointing at the correct file.
- Unsupported opcode warnings – the disassembler prints comments for
unrecognised instructions; add tests and extend
opcodes_generated.rsif you observe new opcodes. - Need structured data for scripting – use the
--format jsonvariants (info,disasm,tokens,decompile) and add--json-compactwhen piping into tools that prefer minified payloads. If you need the manifest path or operand types, consume the structured fields exposed in the JSON report (manifest_path,operand_kind,operand_value). - Unexpected native-contract warnings – the CLI resolves method tokens to
native hashes; when you see
native contract ... does not expose method ..., double-check the target contract name or regenerate the NEF to ensure the method token is valid. (These also appear in the JSONwarningsarray; other warning types may be added in the future.)
JSON schema overview
Each --format json command emits a top-level object containing:
file: Path to the NEF file being inspected.manifest_path: Optional path to the manifest file that was consumed.warnings: Array of human-readable warnings (currently populated when method tokens refer to unknown native methods).- Command-specific payloads:
info: checksum, script hashes,method_tokens(with native annotations) andmanifestsummaries (methods, events, permissions, trusts).disasm:instructionsarray withoffset,opcode,operand_kind, and structuredoperand_value.decompile: combines the disassembly,high_leveltext,csharpview,pseudocode, andmethod_tokensinto one report (C# view carries manifest extras such as Author/Email when provided).tokens: standalonemethod_tokensarray for quick inspection.
Example (excerpt from info --format json):
Formal schema files live under docs/schema for every JSON command
(info.schema.json, disasm.schema.json, decompile.schema.json, tokens.schema.json).
See docs/schema/README.md for versioning guarantees,
validation instructions, and per-command details. Use
neo-decompiler schema --list to discover the available schemas (with version,
path, and description) or --list-json for machine-readable listings, and
neo-decompiler schema <info|disasm|decompile|tokens> (optionally with
--json-compact, --output schema.json, or --quiet) to print or persist
them without cloning the repository.
To validate an existing JSON report:
# or pipe via stdin (suppress schema body with --quiet / --no-print)
Development
If you use just, the repository ships with a
Justfile providing shortcuts for the common workflows above.
Issues and pull requests are welcome if they keep the project lean and focused.
Contributing
See CONTRIBUTING.md for development guidelines and
CODE_OF_CONDUCT.md for behavioural expectations.
Support & Security
- Support channels are documented in
SUPPORT.md. - Responsible disclosure guidance lives in
SECURITY.md.
Changelog
Recent project history is tracked in CHANGELOG.md.
Current version: v0.1.0 (2025-11-26)
Minimum supported Rust version
The crate is tested against Rust 1.70 and newer on CI. Older toolchains are
not guaranteed to work.
License
Dual licensed under MIT or Apache-2.0.
See LICENSE-MIT and LICENSE-APACHE for details.