Spaces:
Sleeping
Sleeping
tanaymitra98 commited on
Commit ·
b214779
0
Parent(s):
Initial commit: MetaXScalar project
Browse files- Dockerfile +14 -0
- HACKATHON_GUIDELINES.md +154 -0
- README.md +65 -0
- app.py +49 -0
- data/manifest.json +176 -0
- data/samples/task1/deprecated_constructor.sol +10 -0
- data/samples/task1/missing_natspec.sol +10 -0
- data/samples/task1/missing_spdx.sol +9 -0
- data/samples/task2/inefficient_loop.sol +14 -0
- data/samples/task2/redundant_storage.sol +14 -0
- data/samples/task2/require_string.sol +9 -0
- data/samples/task3/missing_access_control.sol +10 -0
- data/samples/task3/reentrancy.sol +18 -0
- data/samples/task3/tx_origin_auth.sol +15 -0
- environment.py +101 -0
- graders.py +78 -0
- idea.md +287 -0
- inference.py +100 -0
- openenv.yaml +66 -0
- requirements.txt +3 -0
Dockerfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt /app/requirements.txt
|
| 6 |
+
RUN pip install --no-cache-dir -r /app/requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY . /app
|
| 9 |
+
|
| 10 |
+
ENV PYTHONUNBUFFERED=1
|
| 11 |
+
|
| 12 |
+
EXPOSE 7860
|
| 13 |
+
|
| 14 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
HACKATHON_GUIDELINES.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Meta x PyTorch Hackathon - Round 1 Guidelines
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
- **Event**: Meta x PyTorch Hackathon by Scaler School of Technology
|
| 6 |
+
- **Theme**: Build OpenEnv environments (Reinforcement Learning)
|
| 7 |
+
- **Registration**: 14th March - 3rd April
|
| 8 |
+
- **Round 1**: 25th March - 8th April
|
| 9 |
+
- **Submission Window Opens**: 28th March
|
| 10 |
+
- **Finale**: 25th-26th April
|
| 11 |
+
- **Submission Deadline**: 8th April 2026, 11:59 PM (confirm timezone on dashboard)
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## Team Structure
|
| 16 |
+
|
| 17 |
+
- **Solo**: Compete individually (locked for Round 1 only)
|
| 18 |
+
- **Team**: 2-3 members. Only team lead fills the team form.
|
| 19 |
+
- Once confirmed, teams cannot be changed.
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## Round 1 Problem Statement
|
| 24 |
+
|
| 25 |
+
Build a complete, real-world OpenEnv environment that an AI agent can learn from through the standard `step()` / `reset()` / `state()` API.
|
| 26 |
+
|
| 27 |
+
### Key Requirements
|
| 28 |
+
|
| 29 |
+
1. **Must simulate a real-world task** (not games or toys)
|
| 30 |
+
2. **Implement full OpenEnv spec**: typed models, `step()/reset()/state()`, `openenv.yaml`
|
| 31 |
+
3. **Minimum 3 tasks** with agent graders (easy → medium → hard, scores/reward 0.0–1.0)
|
| 32 |
+
4. **Meaningful reward function** with partial progress signals
|
| 33 |
+
5. **Baseline inference script** with reproducible scores
|
| 34 |
+
6. **Deploy to Hugging Face Spaces** + working Dockerfile
|
| 35 |
+
7. **README** with environment description, action/observation spaces, setup instructions
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## Evaluation Criteria
|
| 40 |
+
|
| 41 |
+
### Pre-Submission Checklist (All Must Pass)
|
| 42 |
+
|
| 43 |
+
| Criteria | Description |
|
| 44 |
+
|----------|-------------|
|
| 45 |
+
| **HF Space deploys** | Automated ping to Space URL must return 200 and respond to `reset()` |
|
| 46 |
+
| **OpenEnv spec compliance** | Validate `openenv.yaml`, typed models, `step()/reset()/state()` endpoints |
|
| 47 |
+
| **Dockerfile builds** | Automated docker build on submitted repo |
|
| 48 |
+
| **Baseline reproduces** | Run inference script — must complete without error and produce scores |
|
| 49 |
+
| **3+ tasks with graders** | Enumerate tasks, run each grader, verify scores/reward in 0.0–1.0 range |
|
| 50 |
+
|
| 51 |
+
---
|
| 52 |
+
|
| 53 |
+
## Mandatory Additional Instructions
|
| 54 |
+
|
| 55 |
+
### Environment Variables (Must be defined)
|
| 56 |
+
|
| 57 |
+
```bash
|
| 58 |
+
API_BASE_URL # The API endpoint for the LLM
|
| 59 |
+
MODEL_NAME # The model identifier to use for inference
|
| 60 |
+
HF_TOKEN # Your Hugging Face API key
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Inference Script Requirements
|
| 64 |
+
|
| 65 |
+
- **Filename**: Must be named `inference.py` in the root directory
|
| 66 |
+
- **LLM Client**: Must use OpenAI Client for all LLM calls
|
| 67 |
+
- **Logging Format**: Must emit structured stdout logs following `[START]`, `[STEP]`, and `[END]` format (field names, ordering, and formatting are strict)
|
| 68 |
+
|
| 69 |
+
### Infrastructure Restrictions
|
| 70 |
+
|
| 71 |
+
- Runtime of inference script should be less than **20 minutes**
|
| 72 |
+
- Must work on a machine with **vCPU=2, memory=8GB**
|
| 73 |
+
|
| 74 |
+
---
|
| 75 |
+
|
| 76 |
+
## Quick Checklist (Must-Haves)
|
| 77 |
+
|
| 78 |
+
- HF Space returns 200 and responds to `reset()`
|
| 79 |
+
- `openenv.yaml` validates; `step()/reset()/state()` endpoints respond correctly
|
| 80 |
+
- Dockerfile builds in CI
|
| 81 |
+
- `inference.py` runs end-to-end and produces scores
|
| 82 |
+
- 3+ tasks with graders; reward in 0.0–1.0 range
|
| 83 |
+
- OpenAI client used for all LLM calls; logs follow strict `[START]/[STEP]/[END]` format
|
| 84 |
+
|
| 85 |
+
---
|
| 86 |
+
|
| 87 |
+
## Preparatory Course (4 Modules ~3.5 hours)
|
| 88 |
+
|
| 89 |
+
| Module | Title | Duration |
|
| 90 |
+
|--------|-------|----------|
|
| 91 |
+
| 1 | Why OpenEnv? | 45 min |
|
| 92 |
+
| 2 | Using Existing Environments | 50 min |
|
| 93 |
+
| 3 | Deploying Environments | 45 min |
|
| 94 |
+
| 4 | Building Your Own Environment | 60 min |
|
| 95 |
+
|
| 96 |
+
**Note**: Each module - read the README first, then open the notebook in Colab. No local setup needed.
|
| 97 |
+
|
| 98 |
+
[Course Repository](https://github.com/raun/openenv-course/tree/main)
|
| 99 |
+
|
| 100 |
+
---
|
| 101 |
+
|
| 102 |
+
## How to Submit
|
| 103 |
+
|
| 104 |
+
1. Complete Step 1 (Team/Solo selection)
|
| 105 |
+
2. Build your OpenEnv environment
|
| 106 |
+
3. Deploy to Hugging Face Spaces
|
| 107 |
+
4. Run pre-submission validation script
|
| 108 |
+
5. Submit via dashboard (only team leaders can submit)
|
| 109 |
+
|
| 110 |
+
---
|
| 111 |
+
|
| 112 |
+
## What Happens After Round 1
|
| 113 |
+
|
| 114 |
+
- Results announced: 10th April
|
| 115 |
+
- Finale: 25th-26th April
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## Need Help?
|
| 120 |
+
|
| 121 |
+
- **Email**: help_openenvhackathon@scaler.com
|
| 122 |
+
- **Discord**: Join the community for announcements, mentor access, and team matching
|
| 123 |
+
- **Discord Link**: https://discord.gg/Dedhy5pkWD
|
| 124 |
+
|
| 125 |
+
---
|
| 126 |
+
|
| 127 |
+
## Example Problem Statement Format
|
| 128 |
+
|
| 129 |
+
> "Build a real-world task environment (e.g., incident triage or logistics scheduling) with clearly defined tasks, automated graders, and reward logic using the OpenEnv framework."
|
| 130 |
+
|
| 131 |
+
### Expected Deliverables:
|
| 132 |
+
- Create an environment an AI agent can interact with
|
| 133 |
+
- Define tasks with increasing difficulty
|
| 134 |
+
- Write graders that verify task completion
|
| 135 |
+
- Define reward logic for scoring
|
| 136 |
+
- Package using OpenEnv for automated evaluation
|
| 137 |
+
|
| 138 |
+
### Evaluation Areas:
|
| 139 |
+
- **Runtime correctness**: Runs without errors
|
| 140 |
+
- **Interface compliance**: Follows OpenEnv standard
|
| 141 |
+
- **Task design**: Clear, realistic, testable
|
| 142 |
+
- **Grading logic**: Reward system makes sense
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
## Resources
|
| 147 |
+
|
| 148 |
+
- [OpenEnv Course Repository](https://github.com/raun/openenv-course/tree/main)
|
| 149 |
+
- [Join Discord Community](https://discord.gg/Dedhy5pkWD)
|
| 150 |
+
- [Contact Support](mailto:help_openenvhackathon@scaler.com)
|
| 151 |
+
|
| 152 |
+
---
|
| 153 |
+
|
| 154 |
+
*Last Updated: April 3, 2026*
|
README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SolidityGuard
|
| 2 |
+
|
| 3 |
+
SolidityGuard is an OpenEnv RL environment that trains agents to review Solidity smart contracts for best practices, gas optimizations, and security vulnerabilities.
|
| 4 |
+
|
| 5 |
+
## Quick Start
|
| 6 |
+
|
| 7 |
+
### Requirements
|
| 8 |
+
- Python 3.11+
|
| 9 |
+
- `API_BASE_URL`, `MODEL_NAME`, `HF_TOKEN` set in the environment
|
| 10 |
+
|
| 11 |
+
### Install
|
| 12 |
+
```bash
|
| 13 |
+
pip install -r requirements.txt
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
### Run Inference
|
| 17 |
+
```bash
|
| 18 |
+
python inference.py
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
### Run API Server
|
| 22 |
+
```bash
|
| 23 |
+
uvicorn app:app --host 0.0.0.0 --port 7860
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
### Expected Output
|
| 27 |
+
Structured logs with `[START]`, `[STEP]`, and `[END]` tags. The final score is reported in the `[END]` log.
|
| 28 |
+
|
| 29 |
+
## Environment Overview
|
| 30 |
+
|
| 31 |
+
### Observation
|
| 32 |
+
- `source_code`: Solidity code string
|
| 33 |
+
- `metadata`: contract name, compiler version, file path
|
| 34 |
+
- `task_id`: active task
|
| 35 |
+
|
| 36 |
+
### Action
|
| 37 |
+
JSON array of findings:
|
| 38 |
+
```json
|
| 39 |
+
[
|
| 40 |
+
{
|
| 41 |
+
"issue_type": "reentrancy",
|
| 42 |
+
"line_number": 13,
|
| 43 |
+
"description": "State updated after external call",
|
| 44 |
+
"severity": "Critical"
|
| 45 |
+
}
|
| 46 |
+
]
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
### Tasks
|
| 50 |
+
- Task 1: Best practices and syntax
|
| 51 |
+
- Task 2: Gas optimization
|
| 52 |
+
- Task 3: Security vulnerabilities
|
| 53 |
+
|
| 54 |
+
## Files
|
| 55 |
+
- `openenv.yaml`: Environment spec
|
| 56 |
+
- `environment.py`: Core env logic (`reset/step/state`)
|
| 57 |
+
- `graders.py`: Reward logic and grading
|
| 58 |
+
- `data/manifest.json`: Dataset manifest
|
| 59 |
+
- `inference.py`: Baseline runner and logging
|
| 60 |
+
- `app.py`: FastAPI endpoints for reset/step/state
|
| 61 |
+
- `Dockerfile`: Container build
|
| 62 |
+
|
| 63 |
+
## Notes
|
| 64 |
+
- Runtime should stay under 20 minutes on 2 vCPU / 8 GB.
|
| 65 |
+
- Docker build must succeed for submission.
|
app.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Any, Dict, List, Optional
|
| 4 |
+
|
| 5 |
+
from fastapi import FastAPI, HTTPException
|
| 6 |
+
from pydantic import BaseModel, Field
|
| 7 |
+
|
| 8 |
+
from environment import SolidityGuardEnv
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
app = FastAPI(title="SolidityGuard")
|
| 12 |
+
env = SolidityGuardEnv()
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class ResetRequest(BaseModel):
|
| 16 |
+
task_id: Optional[str] = Field(default=None)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class StepRequest(BaseModel):
|
| 20 |
+
action: List[Dict[str, Any]]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
@app.get("/health")
|
| 24 |
+
def health() -> Dict[str, str]:
|
| 25 |
+
return {"status": "ok"}
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@app.post("/reset")
|
| 29 |
+
def reset(request: ResetRequest) -> Dict[str, Any]:
|
| 30 |
+
try:
|
| 31 |
+
return env.reset(task_id=request.task_id)
|
| 32 |
+
except Exception as exc:
|
| 33 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@app.post("/step")
|
| 37 |
+
def step(request: StepRequest) -> Dict[str, Any]:
|
| 38 |
+
try:
|
| 39 |
+
return env.step(request.action)
|
| 40 |
+
except Exception as exc:
|
| 41 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@app.get("/state")
|
| 45 |
+
def state() -> Dict[str, Any]:
|
| 46 |
+
try:
|
| 47 |
+
return env.state()
|
| 48 |
+
except Exception as exc:
|
| 49 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
data/manifest.json
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": "t1_sample_1",
|
| 4 |
+
"task_id": "task_1_best_practices",
|
| 5 |
+
"source_path": "data/samples/task1/missing_spdx.sol",
|
| 6 |
+
"metadata": {
|
| 7 |
+
"contract_name": "NoSpdx",
|
| 8 |
+
"compiler_version": "0.7.6",
|
| 9 |
+
"file_path": "missing_spdx.sol"
|
| 10 |
+
},
|
| 11 |
+
"labels": [
|
| 12 |
+
{
|
| 13 |
+
"issue_type": "missing_spdx",
|
| 14 |
+
"line_number": 1,
|
| 15 |
+
"description": "Missing SPDX license identifier",
|
| 16 |
+
"severity": "Low"
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"issue_type": "old_compiler_version",
|
| 20 |
+
"line_number": 2,
|
| 21 |
+
"description": "Compiler version below 0.8.x",
|
| 22 |
+
"severity": "Low"
|
| 23 |
+
}
|
| 24 |
+
]
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"id": "t1_sample_2",
|
| 28 |
+
"task_id": "task_1_best_practices",
|
| 29 |
+
"source_path": "data/samples/task1/missing_natspec.sol",
|
| 30 |
+
"metadata": {
|
| 31 |
+
"contract_name": "NoNatSpec",
|
| 32 |
+
"compiler_version": "0.8.13",
|
| 33 |
+
"file_path": "missing_natspec.sol"
|
| 34 |
+
},
|
| 35 |
+
"labels": [
|
| 36 |
+
{
|
| 37 |
+
"issue_type": "missing_natspec",
|
| 38 |
+
"line_number": 6,
|
| 39 |
+
"description": "Public function missing NatSpec comment",
|
| 40 |
+
"severity": "Low"
|
| 41 |
+
}
|
| 42 |
+
]
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"id": "t1_sample_3",
|
| 46 |
+
"task_id": "task_1_best_practices",
|
| 47 |
+
"source_path": "data/samples/task1/deprecated_constructor.sol",
|
| 48 |
+
"metadata": {
|
| 49 |
+
"contract_name": "LegacyConstructor",
|
| 50 |
+
"compiler_version": "0.4.24",
|
| 51 |
+
"file_path": "deprecated_constructor.sol"
|
| 52 |
+
},
|
| 53 |
+
"labels": [
|
| 54 |
+
{
|
| 55 |
+
"issue_type": "deprecated_constructor",
|
| 56 |
+
"line_number": 6,
|
| 57 |
+
"description": "Constructor uses deprecated syntax",
|
| 58 |
+
"severity": "Low"
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"issue_type": "old_compiler_version",
|
| 62 |
+
"line_number": 2,
|
| 63 |
+
"description": "Compiler version below 0.8.x",
|
| 64 |
+
"severity": "Low"
|
| 65 |
+
}
|
| 66 |
+
]
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"id": "t2_sample_1",
|
| 70 |
+
"task_id": "task_2_gas_optimization",
|
| 71 |
+
"source_path": "data/samples/task2/inefficient_loop.sol",
|
| 72 |
+
"metadata": {
|
| 73 |
+
"contract_name": "LoopGas",
|
| 74 |
+
"compiler_version": "0.8.17",
|
| 75 |
+
"file_path": "inefficient_loop.sol"
|
| 76 |
+
},
|
| 77 |
+
"labels": [
|
| 78 |
+
{
|
| 79 |
+
"issue_type": "unbounded_loop",
|
| 80 |
+
"line_number": 10,
|
| 81 |
+
"description": "Loop uses dynamic array length without bounds",
|
| 82 |
+
"severity": "Medium"
|
| 83 |
+
}
|
| 84 |
+
]
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"id": "t2_sample_2",
|
| 88 |
+
"task_id": "task_2_gas_optimization",
|
| 89 |
+
"source_path": "data/samples/task2/redundant_storage.sol",
|
| 90 |
+
"metadata": {
|
| 91 |
+
"contract_name": "StorageGas",
|
| 92 |
+
"compiler_version": "0.8.19",
|
| 93 |
+
"file_path": "redundant_storage.sol"
|
| 94 |
+
},
|
| 95 |
+
"labels": [
|
| 96 |
+
{
|
| 97 |
+
"issue_type": "redundant_storage_read",
|
| 98 |
+
"line_number": 12,
|
| 99 |
+
"description": "Repeated storage reads could be cached",
|
| 100 |
+
"severity": "Medium"
|
| 101 |
+
}
|
| 102 |
+
]
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"id": "t2_sample_3",
|
| 106 |
+
"task_id": "task_2_gas_optimization",
|
| 107 |
+
"source_path": "data/samples/task2/require_string.sol",
|
| 108 |
+
"metadata": {
|
| 109 |
+
"contract_name": "RequireString",
|
| 110 |
+
"compiler_version": "0.8.18",
|
| 111 |
+
"file_path": "require_string.sol"
|
| 112 |
+
},
|
| 113 |
+
"labels": [
|
| 114 |
+
{
|
| 115 |
+
"issue_type": "custom_error_missing",
|
| 116 |
+
"line_number": 6,
|
| 117 |
+
"description": "Custom errors are preferred over require strings",
|
| 118 |
+
"severity": "Low"
|
| 119 |
+
}
|
| 120 |
+
]
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"id": "t3_sample_1",
|
| 124 |
+
"task_id": "task_3_security",
|
| 125 |
+
"source_path": "data/samples/task3/reentrancy.sol",
|
| 126 |
+
"metadata": {
|
| 127 |
+
"contract_name": "Reentry",
|
| 128 |
+
"compiler_version": "0.8.20",
|
| 129 |
+
"file_path": "reentrancy.sol"
|
| 130 |
+
},
|
| 131 |
+
"labels": [
|
| 132 |
+
{
|
| 133 |
+
"issue_type": "reentrancy",
|
| 134 |
+
"line_number": 13,
|
| 135 |
+
"description": "State update after external call allows reentrancy",
|
| 136 |
+
"severity": "Critical"
|
| 137 |
+
}
|
| 138 |
+
]
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
"id": "t3_sample_2",
|
| 142 |
+
"task_id": "task_3_security",
|
| 143 |
+
"source_path": "data/samples/task3/missing_access_control.sol",
|
| 144 |
+
"metadata": {
|
| 145 |
+
"contract_name": "Admin",
|
| 146 |
+
"compiler_version": "0.8.20",
|
| 147 |
+
"file_path": "missing_access_control.sol"
|
| 148 |
+
},
|
| 149 |
+
"labels": [
|
| 150 |
+
{
|
| 151 |
+
"issue_type": "missing_access_control",
|
| 152 |
+
"line_number": 9,
|
| 153 |
+
"description": "Sensitive function lacks access control",
|
| 154 |
+
"severity": "Critical"
|
| 155 |
+
}
|
| 156 |
+
]
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"id": "t3_sample_3",
|
| 160 |
+
"task_id": "task_3_security",
|
| 161 |
+
"source_path": "data/samples/task3/tx_origin_auth.sol",
|
| 162 |
+
"metadata": {
|
| 163 |
+
"contract_name": "OriginAuth",
|
| 164 |
+
"compiler_version": "0.8.20",
|
| 165 |
+
"file_path": "tx_origin_auth.sol"
|
| 166 |
+
},
|
| 167 |
+
"labels": [
|
| 168 |
+
{
|
| 169 |
+
"issue_type": "tx_origin_auth",
|
| 170 |
+
"line_number": 11,
|
| 171 |
+
"description": "Authorization uses tx.origin",
|
| 172 |
+
"severity": "Critical"
|
| 173 |
+
}
|
| 174 |
+
]
|
| 175 |
+
}
|
| 176 |
+
]
|
data/samples/task1/deprecated_constructor.sol
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: MIT
|
| 2 |
+
pragma solidity ^0.4.24;
|
| 3 |
+
|
| 4 |
+
contract LegacyConstructor {
|
| 5 |
+
uint256 public value;
|
| 6 |
+
|
| 7 |
+
function LegacyConstructor(uint256 initial) public {
|
| 8 |
+
value = initial;
|
| 9 |
+
}
|
| 10 |
+
}
|
data/samples/task1/missing_natspec.sol
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: MIT
|
| 2 |
+
pragma solidity ^0.8.13;
|
| 3 |
+
|
| 4 |
+
contract NoNatSpec {
|
| 5 |
+
uint256 public total;
|
| 6 |
+
|
| 7 |
+
function add(uint256 value) public {
|
| 8 |
+
total += value;
|
| 9 |
+
}
|
| 10 |
+
}
|
data/samples/task1/missing_spdx.sol
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pragma solidity ^0.7.6;
|
| 2 |
+
|
| 3 |
+
contract NoSpdx {
|
| 4 |
+
uint256 public count;
|
| 5 |
+
|
| 6 |
+
function increment() public {
|
| 7 |
+
count += 1;
|
| 8 |
+
}
|
| 9 |
+
}
|
data/samples/task2/inefficient_loop.sol
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: MIT
|
| 2 |
+
pragma solidity ^0.8.17;
|
| 3 |
+
|
| 4 |
+
contract LoopGas {
|
| 5 |
+
uint256[] public values;
|
| 6 |
+
|
| 7 |
+
function sum() public view returns (uint256) {
|
| 8 |
+
uint256 total = 0;
|
| 9 |
+
for (uint256 i = 0; i < values.length; i++) {
|
| 10 |
+
total += values[i];
|
| 11 |
+
}
|
| 12 |
+
return total;
|
| 13 |
+
}
|
| 14 |
+
}
|
data/samples/task2/redundant_storage.sol
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: MIT
|
| 2 |
+
pragma solidity ^0.8.19;
|
| 3 |
+
|
| 4 |
+
contract StorageGas {
|
| 5 |
+
uint256 public price;
|
| 6 |
+
uint256 public fee;
|
| 7 |
+
|
| 8 |
+
function totalCost(uint256 quantity) public view returns (uint256) {
|
| 9 |
+
uint256 cost = price * quantity;
|
| 10 |
+
cost = cost + fee;
|
| 11 |
+
cost = cost + fee;
|
| 12 |
+
return cost;
|
| 13 |
+
}
|
| 14 |
+
}
|
data/samples/task2/require_string.sol
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: MIT
|
| 2 |
+
pragma solidity ^0.8.18;
|
| 3 |
+
|
| 4 |
+
contract RequireString {
|
| 5 |
+
function update(uint256 amount) public pure returns (uint256) {
|
| 6 |
+
require(amount > 0, "amount must be positive");
|
| 7 |
+
return amount;
|
| 8 |
+
}
|
| 9 |
+
}
|
data/samples/task3/missing_access_control.sol
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: MIT
|
| 2 |
+
pragma solidity ^0.8.20;
|
| 3 |
+
|
| 4 |
+
contract Admin {
|
| 5 |
+
mapping(address => bool) public admins;
|
| 6 |
+
|
| 7 |
+
function setAdmin(address account, bool status) public {
|
| 8 |
+
admins[account] = status;
|
| 9 |
+
}
|
| 10 |
+
}
|
data/samples/task3/reentrancy.sol
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: MIT
|
| 2 |
+
pragma solidity ^0.8.20;
|
| 3 |
+
|
| 4 |
+
contract Reentry {
|
| 5 |
+
mapping(address => uint256) public balances;
|
| 6 |
+
|
| 7 |
+
function deposit() public payable {
|
| 8 |
+
balances[msg.sender] += msg.value;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
function withdraw() public {
|
| 12 |
+
uint256 amount = balances[msg.sender];
|
| 13 |
+
require(amount > 0);
|
| 14 |
+
(bool success, ) = msg.sender.call{value: amount}("");
|
| 15 |
+
require(success);
|
| 16 |
+
balances[msg.sender] = 0;
|
| 17 |
+
}
|
| 18 |
+
}
|
data/samples/task3/tx_origin_auth.sol
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SPDX-License-Identifier: MIT
|
| 2 |
+
pragma solidity ^0.8.20;
|
| 3 |
+
|
| 4 |
+
contract OriginAuth {
|
| 5 |
+
address public owner;
|
| 6 |
+
|
| 7 |
+
constructor() {
|
| 8 |
+
owner = msg.sender;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
function withdraw(uint256 amount) public {
|
| 12 |
+
require(tx.origin == owner);
|
| 13 |
+
payable(msg.sender).transfer(amount);
|
| 14 |
+
}
|
| 15 |
+
}
|
environment.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Any, Dict, List, Optional
|
| 7 |
+
|
| 8 |
+
from graders import grade_action
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class EnvironmentState:
|
| 13 |
+
task_id: str
|
| 14 |
+
step_count: int
|
| 15 |
+
max_steps: int
|
| 16 |
+
score_so_far: float
|
| 17 |
+
done: bool
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class SolidityGuardEnv:
|
| 21 |
+
def __init__(self, data_path: str = "data/manifest.json") -> None:
|
| 22 |
+
self.data_path = data_path
|
| 23 |
+
self._load_manifest()
|
| 24 |
+
self._index = 0
|
| 25 |
+
self._current_sample: Optional[Dict[str, Any]] = None
|
| 26 |
+
self._state: Optional[EnvironmentState] = None
|
| 27 |
+
|
| 28 |
+
def _load_manifest(self) -> None:
|
| 29 |
+
if not os.path.exists(self.data_path):
|
| 30 |
+
raise FileNotFoundError(f"Manifest not found: {self.data_path}")
|
| 31 |
+
with open(self.data_path, "r", encoding="utf-8") as handle:
|
| 32 |
+
self._manifest = json.load(handle)
|
| 33 |
+
if not isinstance(self._manifest, list) or not self._manifest:
|
| 34 |
+
raise ValueError("Manifest must be a non-empty list")
|
| 35 |
+
|
| 36 |
+
def reset(self, task_id: Optional[str] = None) -> Dict[str, Any]:
|
| 37 |
+
if task_id:
|
| 38 |
+
candidates = [item for item in self._manifest if item["task_id"] == task_id]
|
| 39 |
+
else:
|
| 40 |
+
candidates = self._manifest
|
| 41 |
+
|
| 42 |
+
if not candidates:
|
| 43 |
+
raise ValueError("No samples available for the requested task")
|
| 44 |
+
|
| 45 |
+
self._current_sample = candidates[self._index % len(candidates)]
|
| 46 |
+
self._index += 1
|
| 47 |
+
|
| 48 |
+
source_path = self._current_sample["source_path"]
|
| 49 |
+
with open(source_path, "r", encoding="utf-8") as handle:
|
| 50 |
+
source_code = handle.read()
|
| 51 |
+
|
| 52 |
+
observation = {
|
| 53 |
+
"source_code": source_code,
|
| 54 |
+
"metadata": self._current_sample.get("metadata", {}),
|
| 55 |
+
"task_id": self._current_sample["task_id"],
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
self._state = EnvironmentState(
|
| 59 |
+
task_id=self._current_sample["task_id"],
|
| 60 |
+
step_count=0,
|
| 61 |
+
max_steps=1,
|
| 62 |
+
score_so_far=0.0,
|
| 63 |
+
done=False,
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
return observation
|
| 67 |
+
|
| 68 |
+
def step(self, action: List[Dict[str, Any]]) -> Dict[str, Any]:
|
| 69 |
+
if self._current_sample is None or self._state is None:
|
| 70 |
+
raise RuntimeError("Call reset() before step().")
|
| 71 |
+
|
| 72 |
+
if self._state.done:
|
| 73 |
+
return {
|
| 74 |
+
"reward": self._state.score_so_far,
|
| 75 |
+
"done": True,
|
| 76 |
+
"details": {"message": "Episode already completed"},
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
expected = self._current_sample.get("labels", [])
|
| 80 |
+
reward, details = grade_action(action, expected)
|
| 81 |
+
|
| 82 |
+
self._state.step_count += 1
|
| 83 |
+
self._state.score_so_far = reward
|
| 84 |
+
self._state.done = True
|
| 85 |
+
|
| 86 |
+
return {
|
| 87 |
+
"reward": reward,
|
| 88 |
+
"done": True,
|
| 89 |
+
"details": details,
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
def state(self) -> Dict[str, Any]:
|
| 93 |
+
if self._state is None:
|
| 94 |
+
raise RuntimeError("Call reset() before state().")
|
| 95 |
+
return {
|
| 96 |
+
"task_id": self._state.task_id,
|
| 97 |
+
"step_count": self._state.step_count,
|
| 98 |
+
"max_steps": self._state.max_steps,
|
| 99 |
+
"score_so_far": self._state.score_so_far,
|
| 100 |
+
"done": self._state.done,
|
| 101 |
+
}
|
graders.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
from typing import Any, Dict, List, Optional, Tuple
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
@dataclass(frozen=True)
|
| 8 |
+
class Issue:
|
| 9 |
+
issue_type: str
|
| 10 |
+
line_number: Optional[int]
|
| 11 |
+
severity: str
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _normalize_issue(issue: Dict[str, Any]) -> Issue:
|
| 15 |
+
return Issue(
|
| 16 |
+
issue_type=str(issue.get("issue_type", "")).strip(),
|
| 17 |
+
line_number=issue.get("line_number"),
|
| 18 |
+
severity=str(issue.get("severity", "")).strip(),
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _match_issue(pred: Issue, expected: Issue) -> bool:
|
| 23 |
+
if pred.issue_type.lower() != expected.issue_type.lower():
|
| 24 |
+
return False
|
| 25 |
+
if pred.severity.lower() != expected.severity.lower():
|
| 26 |
+
return False
|
| 27 |
+
return True
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _line_bonus(pred_line: Optional[int], exp_line: Optional[int]) -> float:
|
| 31 |
+
if pred_line is None or exp_line is None:
|
| 32 |
+
return 0.0
|
| 33 |
+
diff = abs(pred_line - exp_line)
|
| 34 |
+
if diff == 0:
|
| 35 |
+
return 0.2
|
| 36 |
+
if diff <= 2:
|
| 37 |
+
return 0.1
|
| 38 |
+
return 0.0
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def grade_action(action: List[Dict[str, Any]], expected: List[Dict[str, Any]]) -> Tuple[float, Dict[str, Any]]:
|
| 42 |
+
expected_issues = [_normalize_issue(item) for item in expected]
|
| 43 |
+
predicted_issues = [_normalize_issue(item) for item in action]
|
| 44 |
+
|
| 45 |
+
matched = 0
|
| 46 |
+
line_bonus_total = 0.0
|
| 47 |
+
expected_used = [False] * len(expected_issues)
|
| 48 |
+
|
| 49 |
+
for pred in predicted_issues:
|
| 50 |
+
found = False
|
| 51 |
+
for idx, exp in enumerate(expected_issues):
|
| 52 |
+
if expected_used[idx]:
|
| 53 |
+
continue
|
| 54 |
+
if _match_issue(pred, exp):
|
| 55 |
+
expected_used[idx] = True
|
| 56 |
+
matched += 1
|
| 57 |
+
line_bonus_total += _line_bonus(pred.line_number, exp.line_number)
|
| 58 |
+
found = True
|
| 59 |
+
break
|
| 60 |
+
if not found:
|
| 61 |
+
continue
|
| 62 |
+
|
| 63 |
+
expected_count = max(len(expected_issues), 1)
|
| 64 |
+
base_score = matched / expected_count
|
| 65 |
+
false_positives = max(len(predicted_issues) - matched, 0)
|
| 66 |
+
fp_penalty = 0.05 * false_positives
|
| 67 |
+
|
| 68 |
+
score = base_score * 0.8 + min(line_bonus_total, 0.2) - fp_penalty
|
| 69 |
+
score = max(min(score, 1.0), 0.0)
|
| 70 |
+
|
| 71 |
+
details = {
|
| 72 |
+
"matched": matched,
|
| 73 |
+
"expected": len(expected_issues),
|
| 74 |
+
"false_positives": false_positives,
|
| 75 |
+
"line_bonus": round(min(line_bonus_total, 0.2), 3),
|
| 76 |
+
"score": round(score, 4),
|
| 77 |
+
}
|
| 78 |
+
return score, details
|
idea.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SolidityGuard - OpenEnv RL Environment
|
| 2 |
+
|
| 3 |
+
## Project Title
|
| 4 |
+
**SolidityGuard** - An OpenEnv RL Environment for Smart Contract Security Review
|
| 5 |
+
|
| 6 |
+
## Problem Statement
|
| 7 |
+
Build an AI agent that learns to review Solidity smart contracts for security vulnerabilities, gas optimization issues, and best practices using the OpenEnv framework.
|
| 8 |
+
|
| 9 |
+
## Why This Project?
|
| 10 |
+
- Real-world utility: Smart contracts hold billions in crypto assets
|
| 11 |
+
- Unique & differentiated: Most RL envs are generic code review, Solidity is specialized
|
| 12 |
+
- Measurable success: Graders can verify against known vulnerability patterns
|
| 13 |
+
- High impact: Automated security review is in high demand in Web3
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## Environment Design
|
| 18 |
+
|
| 19 |
+
### Observation Space
|
| 20 |
+
- **Input**: Solidity source code (string)
|
| 21 |
+
- **Metadata**: Contract name, compiler version, file path
|
| 22 |
+
|
| 23 |
+
### Action Space
|
| 24 |
+
- **Output**: Review comments with severity levels (Critical/Medium/Low/Info)
|
| 25 |
+
- **Format**: JSON with `issue_type`, `line_number`, `description`, `severity`
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## 3 Tasks (Difficulty Levels)
|
| 30 |
+
|
| 31 |
+
| Task | Difficulty | Focus Area | Examples | Max Score |
|
| 32 |
+
|------|------------|------------|----------|-----------|
|
| 33 |
+
| Task 1 | Easy | Syntax & Best Practices | Missing SPDX license, old compiler version, missing NatSpec | 0.33 |
|
| 34 |
+
| Task 2 | Medium | Gas Optimization | Unchecked external calls, inefficient loops, redundant storage reads | 0.33 |
|
| 35 |
+
| Task 3 | Hard | Security Vulnerabilities | Reentrancy, integer overflow, access control issues, tx.origin usage | 0.34 |
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## Grading System
|
| 40 |
+
|
| 41 |
+
### Task 1: Syntax & Best Practices (0-0.33)
|
| 42 |
+
Grader checks for:
|
| 43 |
+
- [ ] SPDX license identifier present
|
| 44 |
+
- [ ] Compiler version specified (>=0.8.0)
|
| 45 |
+
- [ ] NatSpec comments on public functions
|
| 46 |
+
- [ ] No deprecated Solidity features
|
| 47 |
+
|
| 48 |
+
### Task 2: Gas Optimization (0-0.33)
|
| 49 |
+
Grader checks for:
|
| 50 |
+
- [ ] Cache storage variables in memory where possible
|
| 51 |
+
- [ ] Use of custom errors instead of require strings
|
| 52 |
+
- [ ] Loop optimization (avoid unbounded loops)
|
| 53 |
+
- [ ] Unchecked external call handling
|
| 54 |
+
|
| 55 |
+
### Task 3: Security Vulnerabilities (0-0.34)
|
| 56 |
+
Grader checks for:
|
| 57 |
+
- [ ] Reentrancy guards on sensitive functions
|
| 58 |
+
- [ ] Integer overflow/underflow protection
|
| 59 |
+
- [ ] Access control on sensitive functions
|
| 60 |
+
- [ ] Safe usage of low-level calls
|
| 61 |
+
- [ ] No tx.origin usage for authorization
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
## Reward Function
|
| 66 |
+
|
| 67 |
+
- **Partial Progress**: Points awarded for each correct issue detected
|
| 68 |
+
- **Penalty**: Points deducted for false positives (wrongly reported issues)
|
| 69 |
+
- **Explanation Bonus**: Extra points for accurate line numbers and helpful descriptions
|
| 70 |
+
- **Final Score**: 0.0 to 1.0 scale
|
| 71 |
+
|
| 72 |
+
---
|
| 73 |
+
|
| 74 |
+
## Tech Stack
|
| 75 |
+
|
| 76 |
+
- Python 3.x
|
| 77 |
+
- OpenEnv framework
|
| 78 |
+
- OpenAI Client for LLM inference
|
| 79 |
+
- Hugging Face Spaces + Docker
|
| 80 |
+
- Environment variables: `API_BASE_URL`, `MODEL_NAME`, `HF_TOKEN`
|
| 81 |
+
|
| 82 |
+
---
|
| 83 |
+
|
| 84 |
+
## Deliverables
|
| 85 |
+
|
| 86 |
+
1. `openenv.yaml` - Environment specification
|
| 87 |
+
2. `environment.py` - Main env implementation with step/reset/state
|
| 88 |
+
3. `inference.py` - LLM inference script with [START]/[STEP]/[END] logging
|
| 89 |
+
4. `Dockerfile` - Container configuration
|
| 90 |
+
5. `README.md` - Setup instructions and documentation
|
| 91 |
+
6. Deployed on Hugging Face Spaces
|
| 92 |
+
|
| 93 |
+
---
|
| 94 |
+
|
| 95 |
+
## Timeline
|
| 96 |
+
|
| 97 |
+
| Day | Task |
|
| 98 |
+
|-----|------|
|
| 99 |
+
| Day 1 | Setup + study OpenEnv templates |
|
| 100 |
+
| Day 2 | Design env spec + grader functions |
|
| 101 |
+
| Day 3 | Implement step/reset/state methods |
|
| 102 |
+
| Day 4 | Create inference.py with logging |
|
| 103 |
+
| Day 5 | Deploy to HF Spaces + validate |
|
| 104 |
+
| Day 6 | Polish + write README |
|
| 105 |
+
| Day 7 | Final validation + submit (Deadline: 8th April) |
|
| 106 |
+
|
| 107 |
+
---
|
| 108 |
+
|
| 109 |
+
## Production-Ready Execution Plan
|
| 110 |
+
|
| 111 |
+
### Objective
|
| 112 |
+
Build **SolidityGuard**, a compliant OpenEnv RL environment where an agent audits Solidity contracts for security, gas optimization, and best practices. Must pass all hackathon validators: OpenEnv spec, graders, Docker build, Hugging Face Space, and reproducible `inference.py` with strict logging.
|
| 113 |
+
|
| 114 |
+
### Scope & Constraints
|
| 115 |
+
- Real-world task; no games or toy problems
|
| 116 |
+
- Must implement OpenEnv spec: typed models + `reset()/step()/state()` + `openenv.yaml`
|
| 117 |
+
- Must include 3 tasks (easy/medium/hard) with graders and rewards in 0.0–1.0
|
| 118 |
+
- `inference.py` must exist in repo root, use OpenAI client, and emit strict `[START]/[STEP]/[END]` logs
|
| 119 |
+
- Runtime < 20 minutes on 2 vCPU / 8 GB
|
| 120 |
+
- Deploy to Hugging Face Spaces + Dockerfile must build
|
| 121 |
+
|
| 122 |
+
### Environment Design Summary
|
| 123 |
+
|
| 124 |
+
**Observation**
|
| 125 |
+
- `source_code: str`
|
| 126 |
+
- `metadata`: contract name, compiler version, file path (optional)
|
| 127 |
+
- `task_id`: which task is active
|
| 128 |
+
|
| 129 |
+
**Action**
|
| 130 |
+
- JSON list of findings:
|
| 131 |
+
- `issue_type` (string enum)
|
| 132 |
+
- `line_number` (int or null)
|
| 133 |
+
- `description` (string)
|
| 134 |
+
- `severity` (Critical/Medium/Low/Info)
|
| 135 |
+
|
| 136 |
+
**State**
|
| 137 |
+
- `task_id`
|
| 138 |
+
- `step_count`
|
| 139 |
+
- `max_steps`
|
| 140 |
+
- `score_so_far`
|
| 141 |
+
- `done`
|
| 142 |
+
|
| 143 |
+
### Task Definitions (3 Levels)
|
| 144 |
+
|
| 145 |
+
**Task 1 (Easy): Best Practices / Syntax**
|
| 146 |
+
- SPDX license presence
|
| 147 |
+
- compiler version >= 0.8.x
|
| 148 |
+
- NatSpec for public functions
|
| 149 |
+
- deprecated patterns detection
|
| 150 |
+
|
| 151 |
+
**Task 2 (Medium): Gas Optimization**
|
| 152 |
+
- cache storage vars
|
| 153 |
+
- custom errors instead of require strings
|
| 154 |
+
- avoid unbounded loops
|
| 155 |
+
- safe/efficient external calls
|
| 156 |
+
|
| 157 |
+
**Task 3 (Hard): Security Vulnerabilities**
|
| 158 |
+
- reentrancy detection
|
| 159 |
+
- missing access control
|
| 160 |
+
- unsafe low-level calls
|
| 161 |
+
- no `tx.origin` auth
|
| 162 |
+
|
| 163 |
+
### Grader Logic
|
| 164 |
+
- Partial credit per correct finding
|
| 165 |
+
- Penalty for false positives
|
| 166 |
+
- Bonus for accurate line numbers (bounded)
|
| 167 |
+
- Normalize per task to 0.0–1.0
|
| 168 |
+
- Aggregate score across tasks
|
| 169 |
+
|
| 170 |
+
### Dataset Plan
|
| 171 |
+
- 12–20 Solidity snippets total (4–6 per task)
|
| 172 |
+
- Each snippet has a ground-truth label set for issues
|
| 173 |
+
- Mix of clean and vulnerable examples
|
| 174 |
+
- Add control samples with no issues to penalize false positives
|
| 175 |
+
|
| 176 |
+
### Implementation Plan (Step-By-Step)
|
| 177 |
+
|
| 178 |
+
**Phase 0 — Scope Lock (0.5 day)**
|
| 179 |
+
- Freeze task rubrics
|
| 180 |
+
- Lock dataset size + sourcing method
|
| 181 |
+
- Define baseline score targets
|
| 182 |
+
|
| 183 |
+
**Phase 1 — Spec & Schema (1 day)**
|
| 184 |
+
- Define typed models for observation/action/state
|
| 185 |
+
- Draft `openenv.yaml` with tasks and endpoints
|
| 186 |
+
- Validate schema alignment
|
| 187 |
+
|
| 188 |
+
**Phase 2 — Environment Core (1–2 days)**
|
| 189 |
+
- Implement `reset()` to provide a task-specific sample
|
| 190 |
+
- Implement `step()` to grade action JSON and produce reward
|
| 191 |
+
- Implement `state()` to surface current status
|
| 192 |
+
- Ensure deterministic responses
|
| 193 |
+
|
| 194 |
+
**Phase 3 — Graders + Reward (1–2 days)**
|
| 195 |
+
- Implement task graders
|
| 196 |
+
- Add partial credit and penalty logic
|
| 197 |
+
- Normalize to 0–1
|
| 198 |
+
- Unit-test grader outputs
|
| 199 |
+
|
| 200 |
+
**Phase 4 — Inference Pipeline (1 day)**
|
| 201 |
+
- Implement `inference.py`
|
| 202 |
+
- Use OpenAI client with `API_BASE_URL`, `MODEL_NAME`, `HF_TOKEN`
|
| 203 |
+
- Emit strict `[START]/[STEP]/[END]` logs with required fields
|
| 204 |
+
- Ensure runtime < 20 min
|
| 205 |
+
|
| 206 |
+
**Phase 5 — Packaging & Deployment (1 day)**
|
| 207 |
+
- Dockerfile builds and runs inference
|
| 208 |
+
- Deploy to Hugging Face Spaces
|
| 209 |
+
- Confirm `/reset` returns 200
|
| 210 |
+
|
| 211 |
+
**Phase 6 — Validation & Hardening (0.5–1 day)**
|
| 212 |
+
- Run pre-submission validation script
|
| 213 |
+
- Add input size checks
|
| 214 |
+
- Add timeouts and safe error handling
|
| 215 |
+
- Pin dependencies for reproducibility
|
| 216 |
+
|
| 217 |
+
**Phase 7 — Documentation & Release (0.5 day)**
|
| 218 |
+
- README with setup, schema, tasks, baseline scores
|
| 219 |
+
- Troubleshooting steps
|
| 220 |
+
- Submission checklist
|
| 221 |
+
|
| 222 |
+
### Required Files
|
| 223 |
+
- `openenv.yaml`
|
| 224 |
+
- `environment.py` (or equivalent)
|
| 225 |
+
- `inference.py` (root)
|
| 226 |
+
- `Dockerfile`
|
| 227 |
+
- `README.md`
|
| 228 |
+
- `dataset/` or `samples/` (Solidity snippets + labels)
|
| 229 |
+
|
| 230 |
+
### Success Criteria
|
| 231 |
+
- HF Space deploys and responds to `/reset`
|
| 232 |
+
- Docker build passes
|
| 233 |
+
- `inference.py` completes under 20 minutes
|
| 234 |
+
- Strict log format compliance
|
| 235 |
+
- All graders return normalized scores
|
| 236 |
+
|
| 237 |
+
---
|
| 238 |
+
|
| 239 |
+
## Sample Test Cases
|
| 240 |
+
|
| 241 |
+
### Test Case 1: Reentrancy Vulnerability
|
| 242 |
+
```solidity
|
| 243 |
+
// SPDX-License-Identifier: MIT
|
| 244 |
+
pragma solidity ^0.8.0;
|
| 245 |
+
|
| 246 |
+
contract Vulnerable {
|
| 247 |
+
mapping(address => uint) balances;
|
| 248 |
+
|
| 249 |
+
function withdraw() public {
|
| 250 |
+
uint bal = balances[msg.sender];
|
| 251 |
+
require(bal > 0);
|
| 252 |
+
|
| 253 |
+
(bool success, ) = msg.sender.call{value: bal}("");
|
| 254 |
+
require(success);
|
| 255 |
+
|
| 256 |
+
balances[msg.sender] = 0;
|
| 257 |
+
}
|
| 258 |
+
}
|
| 259 |
+
```
|
| 260 |
+
**Expected**: Agent should detect reentrancy vulnerability (Task 3)
|
| 261 |
+
|
| 262 |
+
### Test Case 2: Missing Access Control
|
| 263 |
+
```solidity
|
| 264 |
+
// SPDX-License-Identifier: MIT
|
| 265 |
+
pragma solidity ^0.8.0;
|
| 266 |
+
|
| 267 |
+
contract AdminContract {
|
| 268 |
+
mapping(address => bool) admins;
|
| 269 |
+
|
| 270 |
+
function setAdmin(address addr, bool status) public {
|
| 271 |
+
admins[addr] = status;
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
```
|
| 275 |
+
**Expected**: Agent should identify missing access control (Task 3)
|
| 276 |
+
|
| 277 |
+
---
|
| 278 |
+
|
| 279 |
+
## References
|
| 280 |
+
|
| 281 |
+
- OpenEnv Framework: https://github.com/raun/openenv-course
|
| 282 |
+
- Common Smart Contract Vulnerabilities: Slither, Mythril
|
| 283 |
+
- Gas Optimization: Solidity docs, OpenZeppelin
|
| 284 |
+
|
| 285 |
+
---
|
| 286 |
+
|
| 287 |
+
*Last Updated: April 3, 2026*
|
inference.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
from typing import Any, Dict, List
|
| 7 |
+
|
| 8 |
+
from environment import SolidityGuardEnv
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def _log(tag: str, payload: Dict[str, Any]) -> None:
|
| 12 |
+
print(f"[{tag}] {json.dumps(payload, ensure_ascii=True)}")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _load_env_var(name: str) -> str:
|
| 16 |
+
value = os.getenv(name)
|
| 17 |
+
if not value:
|
| 18 |
+
raise RuntimeError(f"Missing required environment variable: {name}")
|
| 19 |
+
return value
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _call_model(prompt: str) -> List[Dict[str, Any]]:
|
| 23 |
+
from openai import OpenAI
|
| 24 |
+
|
| 25 |
+
api_base_url = _load_env_var("API_BASE_URL")
|
| 26 |
+
model_name = _load_env_var("MODEL_NAME")
|
| 27 |
+
hf_token = _load_env_var("HF_TOKEN")
|
| 28 |
+
|
| 29 |
+
client = OpenAI(base_url=api_base_url, api_key=hf_token)
|
| 30 |
+
response = client.chat.completions.create(
|
| 31 |
+
model=model_name,
|
| 32 |
+
messages=[
|
| 33 |
+
{"role": "system", "content": "You are a Solidity security reviewer."},
|
| 34 |
+
{"role": "user", "content": prompt},
|
| 35 |
+
],
|
| 36 |
+
temperature=0.0,
|
| 37 |
+
max_tokens=800,
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
content = response.choices[0].message.content or "[]"
|
| 41 |
+
try:
|
| 42 |
+
parsed = json.loads(content)
|
| 43 |
+
except json.JSONDecodeError:
|
| 44 |
+
parsed = []
|
| 45 |
+
if isinstance(parsed, list):
|
| 46 |
+
return parsed
|
| 47 |
+
return []
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _build_prompt(source_code: str, task_id: str) -> str:
|
| 51 |
+
return (
|
| 52 |
+
"Review the Solidity contract and return a JSON array of findings. "
|
| 53 |
+
"Each finding must include: issue_type, line_number, description, severity. "
|
| 54 |
+
f"Task: {task_id}.\n\n"
|
| 55 |
+
f"Contract:\n{source_code}"
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def run() -> int:
|
| 60 |
+
env = SolidityGuardEnv()
|
| 61 |
+
tasks = [
|
| 62 |
+
"task_1_best_practices",
|
| 63 |
+
"task_2_gas_optimization",
|
| 64 |
+
"task_3_security",
|
| 65 |
+
]
|
| 66 |
+
|
| 67 |
+
_log("START", {"task_count": len(tasks)})
|
| 68 |
+
total_score = 0.0
|
| 69 |
+
|
| 70 |
+
for task_id in tasks:
|
| 71 |
+
observation = env.reset(task_id=task_id)
|
| 72 |
+
prompt = _build_prompt(observation["source_code"], task_id)
|
| 73 |
+
actions = _call_model(prompt)
|
| 74 |
+
|
| 75 |
+
step_result = env.step(actions)
|
| 76 |
+
state = env.state()
|
| 77 |
+
|
| 78 |
+
total_score += step_result["reward"]
|
| 79 |
+
|
| 80 |
+
_log(
|
| 81 |
+
"STEP",
|
| 82 |
+
{
|
| 83 |
+
"task_id": task_id,
|
| 84 |
+
"reward": step_result["reward"],
|
| 85 |
+
"details": step_result.get("details", {}),
|
| 86 |
+
"state": state,
|
| 87 |
+
},
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
final_score = round(total_score / len(tasks), 4)
|
| 91 |
+
_log("END", {"final_score": final_score})
|
| 92 |
+
return 0
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
if __name__ == "__main__":
|
| 96 |
+
try:
|
| 97 |
+
sys.exit(run())
|
| 98 |
+
except Exception as exc:
|
| 99 |
+
_log("END", {"final_score": 0.0, "error": str(exc)})
|
| 100 |
+
sys.exit(1)
|
openenv.yaml
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: SolidityGuard
|
| 2 |
+
version: 0.1.0
|
| 3 |
+
description: OpenEnv RL environment for Solidity security review
|
| 4 |
+
entrypoint: environment.py:SolidityGuardEnv
|
| 5 |
+
tasks:
|
| 6 |
+
- id: task_1_best_practices
|
| 7 |
+
difficulty: easy
|
| 8 |
+
description: Detect syntax and best-practice issues in Solidity contracts.
|
| 9 |
+
max_steps: 1
|
| 10 |
+
- id: task_2_gas_optimization
|
| 11 |
+
difficulty: medium
|
| 12 |
+
description: Detect gas optimization opportunities in Solidity contracts.
|
| 13 |
+
max_steps: 1
|
| 14 |
+
- id: task_3_security
|
| 15 |
+
difficulty: hard
|
| 16 |
+
description: Detect security vulnerabilities in Solidity contracts.
|
| 17 |
+
max_steps: 1
|
| 18 |
+
schemas:
|
| 19 |
+
observation:
|
| 20 |
+
type: object
|
| 21 |
+
required: [source_code, metadata, task_id]
|
| 22 |
+
properties:
|
| 23 |
+
source_code:
|
| 24 |
+
type: string
|
| 25 |
+
metadata:
|
| 26 |
+
type: object
|
| 27 |
+
required: [contract_name, compiler_version, file_path]
|
| 28 |
+
properties:
|
| 29 |
+
contract_name:
|
| 30 |
+
type: string
|
| 31 |
+
compiler_version:
|
| 32 |
+
type: string
|
| 33 |
+
file_path:
|
| 34 |
+
type: string
|
| 35 |
+
task_id:
|
| 36 |
+
type: string
|
| 37 |
+
action:
|
| 38 |
+
type: array
|
| 39 |
+
items:
|
| 40 |
+
type: object
|
| 41 |
+
required: [issue_type, line_number, description, severity]
|
| 42 |
+
properties:
|
| 43 |
+
issue_type:
|
| 44 |
+
type: string
|
| 45 |
+
line_number:
|
| 46 |
+
type: integer
|
| 47 |
+
nullable: true
|
| 48 |
+
description:
|
| 49 |
+
type: string
|
| 50 |
+
severity:
|
| 51 |
+
type: string
|
| 52 |
+
enum: [Critical, Medium, Low, Info]
|
| 53 |
+
state:
|
| 54 |
+
type: object
|
| 55 |
+
required: [task_id, step_count, max_steps, score_so_far, done]
|
| 56 |
+
properties:
|
| 57 |
+
task_id:
|
| 58 |
+
type: string
|
| 59 |
+
step_count:
|
| 60 |
+
type: integer
|
| 61 |
+
max_steps:
|
| 62 |
+
type: integer
|
| 63 |
+
score_so_far:
|
| 64 |
+
type: number
|
| 65 |
+
done:
|
| 66 |
+
type: boolean
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.110.0
|
| 2 |
+
openai>=1.30.0
|
| 3 |
+
uvicorn>=0.27.0
|