| Crates.io | neo-decompiler |
| lib.rs | neo-decompiler |
| version | 0.1.0 |
| created_at | 2025-11-25 23:54:55.688015+00 |
| updated_at | 2025-11-25 23:54:55.688015+00 |
| description | Minimal tooling for inspecting Neo N3 NEF bytecode |
| homepage | https://github.com/r3e-network/neo-decompiler |
| repository | https://github.com/r3e-network/neo-decompiler |
| max_upload_size | |
| id | 1950671 |
| size | 407,593 |
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.
tools/generate_opcodes.py).manifest.json) with ABI, feature, group, permission, and trust details that surface in both text and JSON outputsPUSH*, arithmetic operations, jumps,
calls, and SYSCALL (tolerant by default; optional fail-fast flag for unknown
opcodes)if/else, for,
while, and do { } while blocks plus emitted break/continue
statements and manifest-derived entry signatures--format csharp) that mirrors the manifest
entry point and emits stubs for additional ABI methodsneo-decompiler) and a reusable library (neo_decompiler)# Build the binary
cargo build --release
# Print header information
./target/release/neo-decompiler info path/to/contract.nef
# Emit machine-readable header information (includes checksum, script hash, ABI, tokens, manifest path)
./target/release/neo-decompiler info --format json path/to/contract.nef
# Decode instructions
./target/release/neo-decompiler disasm path/to/contract.nef
# Fail fast on unknown opcodes (default is tolerant)
./target/release/neo-decompiler disasm --fail-on-unknown-opcodes path/to/contract.nef
# Machine-readable disassembly (tolerant by default)
./target/release/neo-decompiler disasm --format json path/to/contract.nef
# Emit the high-level contract view (auto-detects `*.manifest.json` if present)
./target/release/neo-decompiler decompile path/to/contract.nef
# Fail fast on unknown opcodes during high-level reconstruction
./target/release/neo-decompiler decompile --fail-on-unknown-opcodes path/to/contract.nef
# Emit the legacy pseudocode listing
./target/release/neo-decompiler decompile --format pseudocode path/to/contract.nef
# Emit a C# contract skeleton (includes manifest extras like Author/Email when present)
./target/release/neo-decompiler decompile --format csharp path/to/contract.nef
# Machine-readable decompilation (high-level, pseudocode, manifest path, metadata)
./target/release/neo-decompiler decompile --format json path/to/contract.nef
# Inspect method tokens
./target/release/neo-decompiler tokens path/to/contract.nef
# Machine-readable tokens listing
./target/release/neo-decompiler tokens --format json path/to/contract.nef
# Use --json-compact alongside any JSON format to minimise whitespace
./target/release/neo-decompiler info --format json --json-compact path/to/contract.nef
Given a manifest snippet:
{
"permissions": [
{ "contract": { "hash": "0x0123..." }, "methods": ["symbol"] },
{ "contract": { "group": "03ABCD..." }, "methods": "*" }
],
"trusts": "*"
}
The info command prints:
Permissions:
- contract=hash:0x0123... methods=["symbol"]
- contract=group:03ABCD... methods=*
Trusts: *
Corresponding JSON (truncated) mirrors the schema:
"manifest": {
"permissions": [
{
"contract": { "type": "Hash", "value": "0x0123..." },
"methods": { "type": "Methods", "value": ["symbol"] }
},
{
"contract": { "type": "Group", "value": "03ABCD..." },
"methods": { "type": "Wildcard", "value": "*" }
}
],
"trusts": { "type": "Wildcard", "value": "*" }
}
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
dotnet tool install -g Neo.Compiler.CSharp
# Compile the example contract
nccs compile \
examples/hello_world/HelloWorld.cs \
--nef build/HelloWorld.nef \
--manifest build/HelloWorld.manifest.json
# Decompile (auto-detects the manifest sitting next to the NEF)
neo-decompiler decompile build/HelloWorld.nef
The examples/README.md file explains the walkthrough and can serve as a
starting point for your own experiments.
cargo install neo-decompiler
Download pre-built binaries from the releases page.
# Install the latest release from git
cargo install --git https://github.com/r3e-network/neo-decompiler --tag v0.1.0 --locked
# Or install the latest development version
cargo install --git https://github.com/r3e-network/neo-decompiler --locked
# Or install locally from a checkout
cargo install --path . --locked
use neo_decompiler::Decompiler;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let decompiler = Decompiler::new();
let result = decompiler.decompile_file_with_manifest(
"contract.nef",
Some("contract.manifest.json"),
)?;
println!("{} instructions", result.instructions.len());
println!("{}", result.high_level);
Ok(())
}
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"]).
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.rs is produced by tools/generate_opcodes.py, which
scrapes the upstream OpCode.cs file and emits every mnemonic alongside its
byte value and operand encoding.src/syscalls_generated.rs originates from tools/data/syscalls.json and
lists each syscall hash, its friendly name, handler, price, and call-flag
mask. crate::syscalls::lookup wires this table into the disassembler and
high-level view so every SYSCALL shows human-readable context.src/native_contracts_generated.rs is generated from
tools/data/native_contracts.json and 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
TestingArtifacts/ to extend coverage:
*.cs) are parsed and rewritten into TestingArtifacts/decompiled/<relative>/.Example.nef + Example.manifest.json) are also picked up automatically (recursively).TestingArtifacts/known_unsupported.txt (one name per line, # for comments, optional path:expected substring to assert the error text); matching artifacts are allowed to fail and are copied to *.error.txt.TestingArtifacts/decompiled/, which is git-ignored by default. Known-unsupported entries are still processed and must emit a non-empty *.error.txt to document the failure reason.TestingArtifacts/edgecases/ (loop lifting, method tokens, manifest metadata, permissions/trusts, call-flag failure, events) and TestingArtifacts/embedded/ (compiler-style C# with embedded manifest/NEF).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.
PUSH* forms, short/long jumps, calls, and SYSCALL). Unrecognised
opcodes still produce informative comments so you can decide how to extend the
decoder.if/else, for, while,
and do { } while loops (including break/continue branches). Complex reconstruction
such as full control-flow graphs or type inference is intentionally out of
scope..manifest.json
file sits next to the NEF or pass it explicitly via --manifest path/to/file.Manifest path: in text, or manifest_path in JSON). If it is
absent/null, pass --manifest path/to/contract.manifest.json explicitly.opcodes_generated.rs if you
observe new opcodes.--format json variants
(info, disasm, tokens, decompile) and add --json-compact when 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).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 JSON warnings array; other
warning types may be added in the future.)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).info: checksum, script hashes, method_tokens (with native annotations)
and manifest summaries (methods, events, permissions, trusts).disasm: instructions array with offset, opcode, operand_kind, and
structured operand_value.decompile: combines the disassembly, high_level text, csharp view,
pseudocode, and method_tokens into one report (C# view carries
manifest extras such as Author/Email when provided).tokens: standalone method_tokens array for quick inspection.Example (excerpt from info --format json):
{
"file": "path/to/contract.nef",
"manifest_path": "path/to/contract.manifest.json",
"manifest": {
"groups": [
{
"pubkey": "03ab...ef",
"signature": "00ff..."
}
],
"permissions": [
{
"contract": { "type": "Hash", "value": "0x0123..." },
"methods": { "type": "Methods", "value": ["symbol"] }
}
],
"trusts": { "type": "Contracts", "value": ["0x89ab..."] }
},
"method_tokens": [
{
"method": "Transfer",
"native_contract": { "contract": "GasToken", "label": "GasToken::Transfer" }
}
],
"warnings": []
}
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:
neo-decompiler info --format json contract.nef > info.json
neo-decompiler schema --schema info --validate info.json
# or pipe via stdin (suppress schema body with --quiet / --no-print)
neo-decompiler schema --schema info --validate - --quiet < info.json
cargo fmt
cargo test
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.
See CONTRIBUTING.md for development guidelines and
CODE_OF_CONDUCT.md for behavioural expectations.
SUPPORT.md.SECURITY.md.Recent project history is tracked in CHANGELOG.md.
Current version: v0.1.0 (2025-11-26)
The crate is tested against Rust 1.70 and newer on CI. Older toolchains are
not guaranteed to work.
Dual licensed under MIT or Apache-2.0.
See LICENSE-MIT and LICENSE-APACHE for details.