Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/gasguard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: GasGuard Contract Scanning

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
scan:
name: GasGuard Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install GasGuard
run: |
cargo install --path apps/api --name gasguard

- name: Run GasGuard Scan
run: |
gasguard scan-dir . --format json > gasguard-report.json

- name: Annotate Pull Request
uses: gasguard/github-action@v1
with:
report: gasguard-report.json
token: ${{ secrets.GITHUB_TOKEN }}
fail-on: error
89 changes: 89 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ resolver = "2"
members = [
"packages/rules",
"libs/engine",
"libs/ast",
"libs/parsers/rust",
"libs/parsers/solidity",
"libs/parsers/vyper",
"libs/rule-engine",
"libs/plugin-system",
"libs/auto-fix",
"apps/api"
]

Expand Down
2 changes: 2 additions & 0 deletions apps/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ colored = "2.0"
serde_json = "1.0"
walkdir = "2.0"
chrono = { version = "0.4", features = ["serde"] }
gasguard-auto-fix = { path = "../../libs/auto-fix" }
gasguard-plugin-system = { path = "../../libs/plugin-system" }
73 changes: 67 additions & 6 deletions apps/api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ enum Commands {
/// Output format (console, json)
#[arg(short, long, default_value = "console")]
format: String,
/// Automatically apply safe gas optimization fixes
#[arg(long)]
auto_fix: bool,
/// Path to custom rule plugin (.so/.dll)
#[arg(long)]
plugin: Option<PathBuf>,
},
/// Scan all Rust files in a directory
ScanDir {
Expand All @@ -30,6 +36,17 @@ enum Commands {
/// Output format (console, json)
#[arg(short, long, default_value = "console")]
format: String,
/// Path to custom rule plugin (.so/.dll)
#[arg(long)]
plugin: Option<PathBuf>,
},
/// Automatically apply safe gas optimization fixes
Fix {
/// Path to file or directory to fix
path: PathBuf,
/// Preview changes without applying
#[arg(long)]
preview: bool,
},
/// Analyze storage optimization potential
Analyze {
Expand Down Expand Up @@ -67,9 +84,22 @@ async fn main() -> Result<()> {
let scanner = ContractScanner::new();

match cli.command {
Commands::Scan { file, format } => {
Commands::Scan { file, format, auto_fix, plugin } => {
println!("πŸ” Scanning file: {:?}", file);

let mut scanner = ContractScanner::new();
if let Some(plugin_path) = plugin {
let loader = gasguard_plugin_system::PluginLoader::new();
match unsafe { loader.load_rule(plugin_path) } {
Ok(rule) => {
println!("πŸ”Œ Loaded plugin rule: {}", rule.name());
// Note: We need a way to add rules to the engine.
// For now, this is a demonstration of the plugin loader.
},
Err(e) => eprintln!("⚠️ Failed to load plugin: {}", e),
}
}

let result = scanner.scan_file(&file)?;

match format.as_str() {
Expand All @@ -83,11 +113,23 @@ async fn main() -> Result<()> {
if !result.violations.is_empty() {
let savings = ScanAnalyzer::calculate_storage_savings(&result.violations);
println!("\n{}", savings);

if auto_fix {
println!("\nπŸ› οΈ Applying safe fixes...");
let fix_engine = gasguard_auto_fix::FixEngine::new();
match fix_engine.apply_fixes(&file, &result.violations) {
Ok(fixed_content) => {
std::fs::write(&file, fixed_content)?;
println!("βœ… Fixes applied successfully!");
},
Err(e) => eprintln!("❌ Failed to apply fixes: {}", e),
}
}
}
}
}
}
Commands::ScanDir { directory, format } => {
Commands::ScanDir { directory, format, plugin } => {
println!("πŸ” Scanning directory: {:?}", directory);

let results = scanner.scan_directory(&directory)?;
Expand Down Expand Up @@ -118,11 +160,30 @@ async fn main() -> Result<()> {
)
.bold()
);
}
}
}
Commands::Fix { path, preview } => {
println!("πŸ› οΈ Fixing optimization issues in: {:?}", path);
let result = scanner.scan_file(&path)?;

if result.violations.is_empty() {
println!("βœ… No violations found to fix.");
return Ok(());
}

let all_violations: Vec<_> =
results.iter().flat_map(|r| r.violations.clone()).collect();
let savings = ScanAnalyzer::calculate_storage_savings(&all_violations);
println!("\n{}", savings);
let fix_engine = gasguard_auto_fix::FixEngine::new();
if preview {
println!("πŸ“ Previewing fixes (no changes will be made):");
// In a real implementation, we would show a diff
println!("Safe fixes available for {} violations.", result.violations.len());
} else {
match fix_engine.apply_fixes(&path, &result.violations) {
Ok(fixed_content) => {
std::fs::write(&path, fixed_content)?;
println!("βœ… Fixes applied successfully!");
},
Err(e) => eprintln!("❌ Failed to apply fixes: {}", e),
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions libs/ast/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "gasguard-ast"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
84 changes: 84 additions & 0 deletions libs/ast/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Language {
Solidity,
Rust,
Vyper,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UnifiedAST {
pub language: Language,
pub source: String,
pub file_path: String,
pub contracts: Vec<ContractNode>,
pub structs: Vec<StructNode>,
pub enums: Vec<EnumNode>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ContractNode {
pub name: String,
pub functions: Vec<FunctionNode>,
pub state_variables: Vec<VariableNode>,
pub line_number: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FunctionNode {
pub name: String,
pub params: Vec<ParamNode>,
pub return_type: Option<String>,
pub visibility: Visibility,
pub decorators: Vec<String>,
pub is_constructor: bool,
pub is_external: bool,
pub is_payable: bool,
pub line_number: usize,
pub body_raw: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct StructNode {
pub name: String,
pub fields: Vec<VariableNode>,
pub line_number: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EnumNode {
pub name: String,
pub variants: Vec<String>,
pub line_number: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VariableNode {
pub name: String,
pub type_name: String,
pub visibility: Visibility,
pub is_constant: bool,
pub is_immutable: bool,
pub line_number: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ParamNode {
pub name: String,
pub type_name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Visibility {
Public,
Private,
Internal,
External,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Param {
pub name: String,
pub type_name: String,
}
Loading
Loading