solidity-vulnerability-detector / upload_eval_dataset.py
jhsu12's picture
Add script to upload Solidity evaluation dataset to Hub
3824970 verified
Raw
History Blame Contribute Delete
10.7 kB
"""
Upload a local evaluation dataset of Solidity files to Hugging Face Hub.
Reads .sol files organized by vulnerability type in subfolders, converts them
into a structured HF Dataset, and pushes to the Hub.
Expected folder structure:
Evaluation_Dataset/
Longer_example/
Reentrancy/
3.sol
4.sol
...
Access Control/
1.sol
2.sol
...
Integer Overflow/
...
Timestamp Dependence/
...
Unchecked Low-Level Calls/
...
Short_example/ (optional β€” additional source folders)
Reentrancy/
...
Output dataset columns:
- code (str): Full Solidity source code
- vulnerability_type (str): Folder name (e.g. "Reentrancy")
- filename (str): Original filename (e.g. "3.sol")
- source (str): Parent folder (e.g. "Longer_example")
- filepath (str): Relative path from root (e.g. "Longer_example/Reentrancy/3.sol")
- num_lines (int): Number of lines in the file
- num_chars (int): Number of characters in the file
Usage:
# Upload with defaults:
python upload_eval_dataset.py --data_dir ./Evaluation_Dataset
# Specify Hub dataset name:
python upload_eval_dataset.py --data_dir ./Evaluation_Dataset --hub_dataset jhsu12/solidity-eval-dataset
# Dry run β€” see what would be uploaded:
python upload_eval_dataset.py --data_dir ./Evaluation_Dataset --dry_run
# Make it private:
python upload_eval_dataset.py --data_dir ./Evaluation_Dataset --private
"""
import argparse
import os
import sys
from datasets import Dataset
from huggingface_hub import create_repo
def parse_args():
parser = argparse.ArgumentParser(
description="Upload Solidity evaluation dataset to Hugging Face Hub."
)
parser.add_argument(
"--data_dir", type=str, required=True,
help="Path to the root evaluation dataset folder"
)
parser.add_argument(
"--hub_dataset", type=str, default="jhsu12/solidity-vulnerability-eval-dataset",
help="Hub dataset ID (default: jhsu12/solidity-vulnerability-eval-dataset)"
)
parser.add_argument(
"--private", action="store_true", default=False,
help="Create as private dataset"
)
parser.add_argument(
"--dry_run", action="store_true", default=False,
help="Show what would be uploaded without actually uploading"
)
return parser.parse_args()
def scan_sol_files(data_dir):
"""
Walk the directory tree and collect all .sol files.
Handles two levels of nesting:
data_dir/VulnType/*.sol β†’ source = "" (flat)
data_dir/Source/VulnType/*.sol β†’ source = Source (nested)
"""
records = []
data_dir = os.path.abspath(data_dir)
for entry in sorted(os.listdir(data_dir)):
entry_path = os.path.join(data_dir, entry)
if not os.path.isdir(entry_path):
continue
# Check if this directory contains .sol files directly (flat: VulnType/*.sol)
sol_files_here = [f for f in os.listdir(entry_path) if f.endswith(".sol")]
subdirs_here = [d for d in os.listdir(entry_path)
if os.path.isdir(os.path.join(entry_path, d))]
if sol_files_here and not subdirs_here:
# Flat: data_dir/VulnType/*.sol
vuln_type = entry
for sol_file in sorted(sol_files_here):
filepath = os.path.join(entry_path, sol_file)
records.append({
"vulnerability_type": vuln_type,
"filename": sol_file,
"source": "",
"filepath": os.path.join(entry, sol_file),
"abs_path": filepath,
})
else:
# Nested: data_dir/Source/VulnType/*.sol
source_name = entry
for vuln_dir in sorted(os.listdir(entry_path)):
vuln_path = os.path.join(entry_path, vuln_dir)
if not os.path.isdir(vuln_path):
continue
vuln_type = vuln_dir
for sol_file in sorted(os.listdir(vuln_path)):
if not sol_file.endswith(".sol"):
continue
filepath = os.path.join(vuln_path, sol_file)
records.append({
"vulnerability_type": vuln_type,
"filename": sol_file,
"source": source_name,
"filepath": os.path.join(source_name, vuln_dir, sol_file),
"abs_path": filepath,
})
return records
def read_sol_files(records):
"""Read the actual Solidity code from each file."""
data = {
"code": [],
"vulnerability_type": [],
"filename": [],
"source": [],
"filepath": [],
"num_lines": [],
"num_chars": [],
}
failed = 0
for rec in records:
try:
with open(rec["abs_path"], "r", encoding="utf-8", errors="replace") as f:
code = f.read()
except Exception as e:
print(f" ⚠️ Failed to read {rec['filepath']}: {e}")
failed += 1
continue
data["code"].append(code)
data["vulnerability_type"].append(rec["vulnerability_type"])
data["filename"].append(rec["filename"])
data["source"].append(rec["source"])
data["filepath"].append(rec["filepath"])
data["num_lines"].append(len(code.splitlines()))
data["num_chars"].append(len(code))
return data, failed
def main():
args = parse_args()
data_dir = os.path.abspath(args.data_dir)
print("=" * 60)
print(" Upload Solidity Evaluation Dataset")
print("=" * 60)
if not os.path.isdir(data_dir):
print(f"\n❌ Directory not found: {data_dir}")
sys.exit(1)
# ── Scan ──────────────────────────────────────────────────────────────────
print(f"\nπŸ” Scanning: {data_dir}")
records = scan_sol_files(data_dir)
if not records:
print(f"\n❌ No .sol files found!")
print(f" Expected structure:")
print(f" {data_dir}/")
print(f" Longer_example/")
print(f" Reentrancy/")
print(f" 3.sol")
print(f" 4.sol")
print(f" Access Control/")
print(f" ...")
sys.exit(1)
# ── Summary ───────────────────────────────────────────────────────────────
# Count by vulnerability type
from collections import Counter
vuln_counts = Counter(r["vulnerability_type"] for r in records)
source_counts = Counter(r["source"] for r in records)
print(f"\n Found {len(records)} .sol files\n")
if any(r["source"] for r in records):
print(f" By source:")
for source, count in sorted(source_counts.items()):
label = source if source else "(root)"
print(f" {label}: {count} files")
print()
print(f" By vulnerability type:")
for vuln, count in sorted(vuln_counts.items()):
print(f" {vuln}: {count} files")
# ── Read files ────────────────────────────────────────────────────────────
print(f"\nπŸ“– Reading Solidity files...")
data, failed = read_sol_files(records)
total = len(data["code"])
if failed:
print(f" ⚠️ {failed} files failed to read")
print(f" βœ… {total} files read successfully")
# Stats
avg_lines = sum(data["num_lines"]) / total if total else 0
avg_chars = sum(data["num_chars"]) / total if total else 0
max_lines = max(data["num_lines"]) if total else 0
min_lines = min(data["num_lines"]) if total else 0
print(f"\n πŸ“Š Stats:")
print(f" Lines: min={min_lines}, avg={avg_lines:.0f}, max={max_lines}")
print(f" Chars: avg={avg_chars:.0f}")
# ── Create dataset ────────────────────────────────────────────────────────
print(f"\nπŸ“¦ Creating HF Dataset...")
dataset = Dataset.from_dict(data)
print(f" {dataset}")
# Show a sample
print(f"\n Sample row:")
sample = dataset[0]
print(f" vulnerability_type: {sample['vulnerability_type']}")
print(f" filename: {sample['filename']}")
print(f" source: {sample['source']}")
print(f" num_lines: {sample['num_lines']}")
print(f" code: {sample['code'][:100]}...")
if args.dry_run:
print(f"\nπŸ” DRY RUN β€” would push to {args.hub_dataset}")
print(f" {total} samples, {len(vuln_counts)} vulnerability types")
sys.exit(0)
# ── Push to Hub ───────────────────────────────────────────────────────────
print(f"\nπŸš€ Pushing to {args.hub_dataset}...")
# Create repo first
try:
create_repo(
args.hub_dataset,
repo_type="dataset",
private=args.private,
exist_ok=True,
)
except Exception as e:
print(f" ❌ Failed to create repo: {e}")
sys.exit(1)
dataset.push_to_hub(args.hub_dataset, private=args.private)
print(f"\n βœ… Pushed to https://huggingface.co/datasets/{args.hub_dataset}")
# ── Done ──────────────────────────────────────────────────────────────────
print(f"\n{'=' * 60}")
print(f" Dataset uploaded!")
print(f" URL: https://huggingface.co/datasets/{args.hub_dataset}")
print(f" Samples: {total}")
print(f" Types: {', '.join(sorted(vuln_counts.keys()))}")
print(f"\n Load it with:")
print(f' from datasets import load_dataset')
print(f' ds = load_dataset("{args.hub_dataset}")')
print(f"{'=' * 60}")
if __name__ == "__main__":
main()