A comprehensive smart contract analysis system combining LLM-powered bytecode decompilation with vulnerability detection, malicious contract classification, and automated security audit report generation.
Paper: Decompiling Smart Contracts with a Large Language Model (arXiv:2506.19624v1)
EVM Bytecode → Static Analysis → TAC → Fine-tuned LLM → Solidity
│ │
├─→ Opcode Feature Extraction → Malicious Classification │
│ │
├─→ CFG Pattern Matching → Vulnerability Detection │
│ ▼
└──────────────────────────────────────────→ Audit Report
The system implements four analysis pipelines coordinated by PipelineOrchestrator:
- Bytecode → TAC → Solidity —
BytecodeAnalyzerdisassembles EVM bytecode, constructs control-flow graphs, and emits TAC; a LoRA fine-tuned LLM translates TAC into readable Solidity - Vulnerability Detection —
VulnerabilityDetectorscans bytecode and source for reentrancy, timestamp dependence, overflow, delegatecall, access control, and selfdestruct vulnerabilities - Malicious Classification —
MaliciousContractClassifieruses opcode frequency features with a LightGBM/LIME path when a fitted artifact is available, otherwise heuristic fallback classification - Audit Report Generation —
AuditReportGeneratoraggregates all findings into a comprehensive security audit with risk scoring
# Core bytecode analysis and dataset preparation; no PyTorch/CUDA dependency
uv sync
# Model training and evaluation with CUDA 13 wheels
uv sync --extra training --extra cuda
# CPU-only development environment, including web and analysis features
uv sync --dev --extra training --extra web --extra analysis --extra cpuRequirements: Python 3.10–3.12. Python 3.11 is recommended on Windows because the current Web3 dependency stack has prebuilt native wheels for it. GPU workloads require a compatible PyTorch/CUDA installation and sufficient memory for the model. CPU-only inference and smoke training are available with
--extra cpu; a 7B model generally needs substantially more memory than a tiny smoke model.
Optional profiles are inference, evaluation, training, quantization,
web, and analysis. Training includes evaluation and inference; quantization
also adds bitsandbytes. Use --extra quantization for the QLoRA runners, and
--extra web for the UI. Add --extra cpu to select the explicit CPU-only
PyTorch index rather than merely hiding CUDA devices.
--extra cuda selects CUDA 13 wheels explicitly and cannot be combined with
--extra cpu. Without either accelerator extra, model profiles use PyPI's
platform default (CUDA on Linux, CPU on Windows/macOS).
Download verified Solidity contracts from HuggingFace (andstor/smart_contracts), compile each with one source-aligned solc/optimizer configuration, generate TAC, and export training pairs:
# Download 20 contracts (quick test)
uv run python download_hf_contracts.py --limit 20
# Download 100 contracts
uv run python download_hf_contracts.py --limit 100
# Full dataset (all available contracts)
uv run python download_hf_contracts.pyPhases (can be run independently):
uv run python download_hf_contracts.py --download-only # Phase 1: Download only
uv run python download_hf_contracts.py --compile-only # Phase 2: Compile & generate TAC
uv run python download_hf_contracts.py --export-only # Phase 3: Export JSONLOutput: data/hf_training_dataset.jsonl — each line is {"input": "<TAC>", "output": "<Solidity>", "metadata": {...}}
# Train on the downloaded dataset (Qwen2.5-Coder-7B-Instruct with LoRA)
uv run --extra training python train.py --skip-collection --dataset data/hf_training_dataset.jsonl
# Quick test (1 epoch, small batch)
uv run --extra training python train.py --skip-collection --dataset data/hf_training_dataset.jsonl --small
# Fast E2E test with a tiny model (no GPU needed)
uv run --extra training --extra cpu python train.py --skip-collection --dataset data/hf_training_dataset.jsonl --tiny
# Full training with custom parameters
uv run --extra training python train.py --skip-collection --dataset data/hf_training_dataset.jsonl \
--epochs 5 --batch-size 1 --lr 2e-4 --max-seq-length 8192
# Force single-GPU or memory-saving quantized LoRA when needed
uv run --extra quantization python train.py --skip-collection --dataset data/hf_training_dataset.jsonl \
--num-gpus 1 --quantizationOutput: Trained model saved to models/; run manifests are written under
models/run_manifests/ by default. Dataset splitting writes
data/split_manifest.json with leakage checks and holdout coverage; JSONL schema
and token-length preflight runs before training/evaluation unless
--skip-data-preflight is used for legacy smoke runs. Preflight fails closed
when the actual tokenizer cannot load unless --allow-whitespace-preflight-fallback
is supplied explicitly.
Evaluation runs automatically after training unless --skip-eval is passed. Results are saved to results/.
Use --eval-batch-size N and --eval-max-new-tokens N to control batched decompilation.
# Train and evaluate
uv run --extra training python train.py --skip-collection --dataset data/hf_training_dataset.jsonl
# Train without evaluation
uv run --extra training python train.py --skip-collection --dataset data/hf_training_dataset.jsonl --skip-eval# Step 1: Download and prepare 20 contracts
uv run python download_hf_contracts.py --limit 20
# Step 2: Train the model on the downloaded data
uv run --extra training python train.py --skip-collection --dataset data/hf_training_dataset.jsonl --small
# Step 3: Check results
ls results/# Generate JSON with Solidity, TAC, timings, function errors, and model config
uv run --extra inference python scripts/decompile.py \
--model-path models/final_model \
--bytecode 0x60806040... \
--format json
# TAC-only analysis does not require a model artifact
uv run python scripts/decompile.py --format tac --bytecode 0x60806040...Production inference assumes only Etherscan Contract > Bytecode is available. Do not pass true compiler version or optimizer settings as prompt inputs. High-value safe inputs are bytecode-derived TAC/CFG, function selector, basic block/branch counts, TAC/op counts, storage read/write counts, external call count, event/log/revert counts, bytecode length/instruction/function counts, and selector/signature guesses only when inferred from bytecode selectors or selector databases and marked inferred.
The web API streams Server-Sent Events from POST /api/decompile:
curl -N http://127.0.0.1:5000/api/decompile \
-H 'Content-Type: application/json' \
-d '{"bytecode":"0x60806040...","generation":{"max_new_tokens":512}}'For the full operator guide, including current-data sufficiency checks, data regeneration, training, evaluation, and model usage, see docs/runbook.md.
The pytest configuration disables Web3's unrelated pytest_ethereum entry
point so repository tests collect consistently in supported uv environments.
Install development dependencies with
uv sync --dev --extra training --extra web --extra analysis --extra cpu
before running tests. The CI matrix runs the complete offline CPU regression
suite on Python 3.10 and 3.12.
# Run all tests
uv run pytest
# Verbose output
uv run pytest -v
# Run specific module tests (examples)
uv run pytest tests/test_bytecode_analyzer.py -v
uv run pytest tests/test_dataset_pipeline.py -v
uv run pytest tests/test_docs_consistency.py -v
uv run pytest tests/test_vulnerability_detector.py -v
uv run pytest tests/test_malicious_classifier.py -v
uv run pytest tests/test_audit_report.py -v
uv run pytest tests/test_pipeline_orchestrator.py -v
uv run pytest tests/test_opcode_features.py -v
uv run pytest tests/test_web_app.py -v
uv run pytest tests/test_e2e.py -v├── download_hf_contracts.py # CLI: Download HuggingFace data → compile → export
├── train.py # CLI: Train & evaluate the decompilation model
├── pyproject.toml # Project metadata, uv dependencies, and tool config
├── uv.lock # Locked Python dependencies generated by uv
│
├── src/ # Core library
│ ├── __init__.py # Package exports (v2.0.0)
│ ├── bytecode_analyzer.py # EVM bytecode → TAC conversion, CFG construction
│ ├── dataset_pipeline.py # Etherscan data collection, Solidity parsing, DB
│ ├── dataset_export_primitives.py # Shared JSONL schema, sanitization, dedup helpers
│ ├── local_compiler.py # Local solc compilation (py-solc-x)
│ ├── model_setup.py # Model config, LoRA fine-tuning, inference
│ ├── inference.py # Shared bytecode inference schema for CLI/web/orchestrator
│ ├── contract_reconstruction.py # Contract scaffold assembly and quality/source summaries
│ ├── abi_enrichment.py # ABI/source-derived annotations kept out of prompts
│ ├── tac_lookup.py # Exact TAC→Solidity lookup database integration
│ ├── training_pipeline.py # Evaluation metrics (similarity, edit distance, replication precision/recall)
│ ├── replication_metrics.py # Structured Solidity fact extraction and replication metrics
│ ├── evaluation_report.py # Human-readable latest-results report writer
│ ├── opcode_features.py # Opcode frequency, TF-IDF, entropy-based features
│ ├── vulnerability_detector.py # Vulnerability scanning (6 types, 5 severities)
│ ├── malicious_classifier.py # Malicious classification + heuristic/LIME explanations
│ ├── audit_report.py # Security audit report generation + risk scoring
│ ├── pipeline_orchestrator.py # Coordinates all analysis stages
│ ├── selector_resolver.py # 4-byte selector → function signature (4-tier lookup)
│ └── settings.yaml.example # Template for optional local API-key config
│
├── tests/ # Unit, integration, docs, and web tests (pytest)
│ ├── test_bytecode_analyzer.py # Bytecode parsing, CFG, TAC, stack sim
│ ├── test_contract_reconstruction.py # Contract assembly and source summaries
│ ├── test_data_generation_export_issues.py # HF export and manifest regressions
│ ├── test_dataset_pipeline.py # Data collection and function pairing
│ ├── test_dataset_quality_issues.py # Dataset quality regression coverage
│ ├── test_decompile_cli.py # CLI inference behavior
│ ├── test_docs_consistency.py # README/runbook workflow drift checks
│ ├── test_evaluation_report.py # Latest-results report formatting
│ ├── test_evaluation_quality_metrics.py # Evaluation/quality metric regressions
│ ├── test_model_training_issues.py # Training pipeline regressions
│ ├── test_opcode_features.py # Feature extraction, TF-IDF, entropy
│ ├── test_train_cli_issues.py # train.py CLI and quality-gate regressions
│ ├── test_prompt_metadata.py # Bytecode-only prompt metadata coverage
│ ├── test_vulnerability_detector.py # Vulnerability scanning
│ ├── test_malicious_classifier.py # Classification and explainability
│ ├── test_audit_report.py # Audit reports and risk scoring
│ ├── test_pipeline_orchestrator.py # Pipeline orchestration
│ ├── test_replication_metrics.py # Research replication metrics
│ ├── test_web_app.py # Web API auth and UI integration
│ └── test_e2e.py # End-to-end integration
│
├── web/ # Flask web application
│ ├── app.py # Server: decompilation, vuln scan, classify, audit
│ ├── templates/index.html # Decompiler UI
│ └── static/ # Frontend assets (app.js, style.css)
│
├── scripts/ # Debug & utility scripts
│ ├── decompile.py # CLI: bytecode → TAC/Solidity inference
│ ├── build_lookup_db.py # Exact TAC lookup database builder
│ ├── run_compiler_metadata_ablation.py # Historical prompt-ablation helper
│ ├── demo.py
│ ├── check_bytecode_format.py
│ ├── inspect_bytecode.py
│ └── debug_*.py # Ad-hoc debugging scripts
│
├── train_common.sh # Shared multi-GPU training config
├── run_train_torchrun.sh # Multi-GPU DDP training (torchrun)
├── run_train_deepspeed.sh # DeepSpeed training
├── ds_config.json # DeepSpeed configuration
│
├── data/ # Generated datasets (gitignored)
├── models/ # Trained models (gitignored)
├── results/ # Evaluation results (gitignored)
├── test_data/ # Test fixtures
├── docs/ # Documentation
│ ├── architecture.md # System design & data flow
│ ├── model-details.md # Model config, LoRA, quantization
│ ├── data-format.md # JSONL schema, TAC format, DB schema
│ ├── training-recommendations.md # Model selection & multi-GPU training
│ ├── runbook.md # E2E training, evaluation, and inference guide
│ └── contributing.md # Development setup & guidelines
├── demo_dataset.jsonl # Smoke-test/demo fixture (55 examples)
└── reference/ # Research paper PDF, enhancement plans
| Flag | Default | Description |
|---|---|---|
--limit N |
0 (all) |
Max contracts to download |
--max-compiler-versions N |
1 |
Deprecated compatibility option; generation always uses one source-aligned compiler config |
--workers N |
auto | Parallel compilation workers |
--max-body-dupes N |
2 |
Max copies of same function body |
--min-body-length N |
50 |
Min Solidity body length |
--cache-dir PATH |
HuggingFace default | Cache directory for Parquet files |
--hf-revision REV |
latest | HuggingFace dataset revision/commit to pin downloads |
--download-only |
— | Only download, skip compilation |
--compile-only |
— | Only compile downloaded contracts |
--export-only |
— | Only export existing pairs to JSONL |
--output PATH |
data/hf_training_dataset.jsonl |
Output file |
--max-seq-length N |
8192 |
Export-time prompt/target length budget |
--export-selectors PATH |
— | Export selector registry JSON |
--import-selectors PATH |
— | Import selector registry JSON before processing |
--db PATH |
data/contracts.db |
SQLite database path |
For the 4-GPU RTX 8000 throughput recipe from
docs/gpu-parameter-sweep-results.md, use
THROUGHPUT_SWEEP_DEFAULTS=true ./run_train_torchrun.sh.
| Flag | Default | Description |
|---|---|---|
--config PATH |
— | YAML/JSON training config; CLI flags override file values |
--skip-collection |
— | Use existing dataset (skip Etherscan) |
--dataset PATH |
auto | Path to a full-source JSONL dataset; cached split artifacts require --allow-split-artifact-source |
--allow-split-artifact-source |
off | Explicitly allow re-splitting cached split artifacts: train_dataset.jsonl, val_dataset.jsonl, validation_dataset.jsonl, or test_dataset.jsonl |
--small |
— | Quick test: 1 epoch, batch=2 |
--tiny |
— | Use facebook/opt-125m for fast testing |
--epochs N |
3 |
Training epochs |
--batch-size N |
1 |
Per-device batch size |
--global-batch-size N |
16 |
Target effective batch size used to auto-compute gradient accumulation |
--gradient-accumulation-steps N |
auto | Override automatic gradient accumulation |
--lr FLOAT |
2e-4 |
Learning rate |
--max-seq-length N |
8192 |
Max token sequence length |
--eval-max-new-tokens N |
256 |
Max generated Solidity tokens per eval example |
--model-name NAME |
Qwen/Qwen2.5-Coder-7B-Instruct |
Base model |
--precision MODE |
fp16 |
Trainer/DeepSpeed precision mode (auto, bf16, fp16, or fp32) |
--report-to BACKEND |
none |
Trainer reporting backend (none, tensorboard, wandb, or all); wrappers default to TensorBoard |
--num-gpus N |
4 |
GPU count for automatic torchrun launch |
--no-auto-torchrun |
off | Do not auto-relaunch with torchrun |
--lora / --no-lora |
on | Enable or disable LoRA adapter training |
--lora-rank N |
16 |
LoRA rank |
--lora-alpha N |
32 |
LoRA alpha |
--lora-dropout FLOAT |
0.1 |
LoRA dropout |
--quantization / --no-quantization |
off | Enable or disable 4-bit NF4 loading |
--gradient-checkpointing / --no-gradient-checkpointing |
on | Activation checkpointing; disable only when trading memory for throughput |
--collection-workers N |
3 |
Etherscan collection worker count when supported |
--max-compiler-configs N |
2 |
Compiler configs per collected contract |
--allow-demo-fallback |
off | Explicitly allow demo_dataset.jsonl when real collection yields no pairs |
--split-seed N |
42 |
Deterministic leakage-free split seed |
--split-manifest PATH |
<data-dir>/split_manifest.json |
Dataset split manifest path |
--skip-split-validation |
off | Skip leakage/coverage split gate |
--min-holdout-stratum-count N |
0 |
Fail if common val/test coverage strata fall below N |
--resume PATH|auto|required |
— | Resume from a valid checkpoint path, latest valid checkpoint-*, or fail if required and none exists |
--skip-eval |
— | Skip post-training evaluation |
--eval-batch-size N |
1 |
Batch size for evaluation decompilation |
--dataset-only |
— | Only build dataset, skip training |
--no-compiler-metadata |
— | Deprecated no-op; compiler/optimizer metadata is never included in prompts |
--no-bytecode-metadata |
off | Disable the compact bytecode/TAC-derived metadata line; TAC sanitization still runs |
--skip-data-preflight |
off | Skip JSONL schema/token-length preflight |
--preflight-tokenizer-download |
off | Allow tokenizer downloads during preflight |
--allow-whitespace-preflight-fallback |
off | Explicitly allow approximate whitespace token counts if the tokenizer cannot load |
--max-steps N |
-1 |
Cap optimizer steps for bounded experiments |
--tokenization-cache |
on | Cache tokenized train/eval datasets with serialized writers |
--manifest-dir PATH |
<output-dir>/run_manifests |
Directory for run manifests |
--run-manifest PATH |
— | Exact manifest JSON path |
--no-throughput-metrics |
off | Disable default throughput telemetry |
--enable-torch-profiler |
off | Write bounded torch profiler traces |
Training wrappers run post-training evaluation by default and write latest_results.txt; set
SKIP_EVAL=true only for smoke/sweep runs. They pass REPORT_TO=tensorboard by default and
persist Trainer log history plus throughput telemetry under --output-dir.
scripts/run_compiler_metadata_ablation.py is historical/deprecated for
production prompt design. Current prompt code ignores compiler metadata, so the
script no longer creates a true compiler-metadata control.
| Flag | Default | Description |
|---|---|---|
--model-path PATH |
Auto | Trained model artifact; otherwise checks WEB_MODEL_PATH, models/final_model/, then newest models/final_model*/ |
--bytecode HEX |
— | EVM bytecode with or without 0x |
--bytecode-file PATH |
— | Read bytecode from a file instead of the command line |
--format json|solidity|tac |
json |
Output machine JSON, Solidity only, or TAC only |
--compiler-version VERSION |
— | Deprecated no-op; ignored for bytecode-only inference |
--optimizer-enabled true|false |
— | Deprecated no-op; ignored for bytecode-only inference |
--optimizer-runs N |
— | Deprecated no-op; ignored for bytecode-only inference |
--evm-version VERSION |
— | Deprecated no-op; ignored for bytecode-only inference |
--max-new-tokens N |
1024 |
Per-function generation cap |
--temperature FLOAT |
0.1 |
Sampling temperature |
--do-sample |
off | Enable stochastic sampling |
--repetition-penalty FLOAT |
1.15 |
Repetition penalty |
--timeout-seconds N |
WEB_DECOMPILE_TIMEOUT_SECONDS or 900 |
Hard model-worker deadline; CPU analysis uses cooperative deadline checks; 0 disables |
--max-functions N |
WEB_MAX_DECOMPILE_FUNCTIONS or 128 |
Abort when more functions are detected |
| Variable | Required | Description |
|---|---|---|
HF_TOKEN |
For gated/private HF models | HuggingFace token for model/tokenizer access or rate-limit reduction |
ETHERSCAN_API_KEY |
For Etherscan collection | Only needed with train.py without --skip-collection |
WEB_API_KEY |
Shared/remote web deployments | Protects API endpoints and GPU telemetry; the browser UI has an in-memory API key field |
WEB_HOST |
Optional | Web bind address, default 127.0.0.1; use with WEB_API_KEY for non-loopback hosts |
WEB_CORS_ORIGINS |
Optional | Comma-separated allowed API origins, default http://127.0.0.1:5000,http://localhost:5000 |
WEB_MODEL_PATH |
Optional | Web/CLI model artifact override before models/final_model/ and newest models/final_model*/ |
WEB_MAX_BYTECODE_HEX_LENGTH |
Optional | Max accepted bytecode hex characters (default 200000) |
WEB_MAX_CONCURRENT_DECOMPILES |
Optional | Concurrent /api/decompile jobs (default 1) |
WEB_MAX_DECOMPILE_FUNCTIONS |
Optional | Max functions per web decompile job (default 128) |
WEB_DECOMPILE_TIMEOUT_SECONDS |
Optional | Hard model-worker timeout, cooperative CPU analysis deadline (default 900; 0 disables) |
WEB_MAX_NEW_TOKENS |
Optional | Upper bound for requested generation.max_new_tokens (default 4096) |
WEB_DEFAULT_MAX_NEW_TOKENS |
Optional | Default web generation cap (default 1024) |
WEB_DEFAULT_TEMPERATURE |
Optional | Default web generation temperature (default 0.1) |
WEB_DEFAULT_DO_SAMPLE |
Optional | Default web sampling mode (default false) |
WEB_DEFAULT_REPETITION_PENALTY |
Optional | Default repetition penalty (default 1.15) |
WEB_ENABLE_REMOTE_SELECTOR_LOOKUP |
Optional | Allow 4byte.directory lookups (default false) |
WEB_INFERENCE_TRACE_ENABLED |
Optional | Write request traces under results/inference_traces/ (default true) |
WEB_INFERENCE_TRACE_INCLUDE_SAMPLES |
Optional | Include raw bytecode/TAC samples in traces (default false) |
WEB_INFERENCE_TRACE_DIR |
Optional | Trace directory (default results/inference_traces/) |
WEB_MAX_ABI_JSON_CHARS |
Optional | Max ABI JSON characters accepted by /api/decompile (default 200000) |
WEB_MAX_CONTRACT_METADATA_JSON_CHARS |
Optional | Max metadata JSON characters (default 200000) |
WEB_MAX_ABI_ENTRIES |
Optional | Max ABI entries (default 512) |
WEB_MODEL_WARMUP_ENABLED |
Optional | Run bounded startup inference warmup (default false; also web/app.py --warmup) |
WEB_MODEL_WARMUP_TIMEOUT_SECONDS |
Optional | Warmup timeout (default 30) |
WEB_MODEL_WARMUP_MAX_NEW_TOKENS |
Optional | Warmup generation cap (default 8) |
Web inference settings are read from environment variables and web/app.py
CLI flags, not src/settings.yaml.
web/app.py also supports --model-path, --remote-selector-lookup,
--host, --port, --debug, --mockmodel, --warmup, and --no-warmup.
/api/health reports the effective limits, generation_defaults, tracing,
warmup, model readiness/path, and lookup status.
Model inference runs in a persistent spawn worker that loads its own model, including on Windows. Timeout or cancellation terminates that worker before releasing model capacity; the next request starts a fresh worker. CPU analysis uses synchronous, cooperative deadline checks rather than abandoned background threads. Health and result payloads report the effective enforcement mode.
CLI, web, and pipeline results distinguish completed, partial, failed, and skipped work. TAC-only analysis is not reported as completed model decompilation; degraded analysis and conflicting reconstructed declarations remain explicit.
- Base model: Qwen2.5-Coder-7B-Instruct (
Qwen/Qwen2.5-Coder-7B-Instruct) - Fine-tuning: LoRA (r=16, α=32, dropout=0.1)
- Quantization: Off by default for full-precision LoRA; enable 4-bit NF4 with
--quantization - Target metrics: Semantic similarity > 0.8, Edit distance < 0.4
See docs/model-details.md for more.
| Problem | Solution |
|---|---|
| CUDA out of memory | Reduce --batch-size or use --tiny |
HF_TOKEN required |
Set HF_TOKEN env var, or copy src/settings.yaml.example to src/settings.yaml and add it there |
| No training pairs generated | Check solc installation: uv run python -c "from solcx import get_installed_solc_versions; print(get_installed_solc_versions())" |
| Download hangs | HuggingFace data is cached after first download; check ~/.cache/huggingface/hub/ |
MIT — see LICENSE