Skip to content

Commit

Permalink
v0.0.7 (#50)
Browse files Browse the repository at this point in the history
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Patrick Collins <54278053+PatrickAlphaC@users.noreply.github.com>
Co-authored-by: VItto Rivabella <72762629+Eversmile12@users.noreply.github.com>
Co-authored-by: Daniel Zarifpour <dzarifpour@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tilak Madichetti <tilak.madichetti@gmail.com>
  • Loading branch information
6 people authored Dec 8, 2023
1 parent 4f1bed4 commit d824cff
Show file tree
Hide file tree
Showing 71 changed files with 846 additions and 757 deletions.
4 changes: 0 additions & 4 deletions .github/workflows/cargo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ jobs:

- name: Run cargo check
uses: actions-rs/cargo@v1
continue-on-error: true # WARNING: only for this example, remove it!
with:
command: check

Expand All @@ -39,7 +38,6 @@ jobs:

- name: Run cargo test
uses: actions-rs/cargo@v1
continue-on-error: true # WARNING: only for this example, remove it!
with:
command: test

Expand All @@ -60,14 +58,12 @@ jobs:

- name: Run cargo fmt
uses: actions-rs/cargo@v1
continue-on-error: true # WARNING: only for this example, remove it!
with:
command: fmt
args: --all -- --check

- name: Run cargo clippy
uses: actions-rs/cargo@v1
continue-on-error: true # WARNING: only for this example, remove it!
with:
command: clippy
args: -- -D warnings
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/target
.DS_Store
12 changes: 11 additions & 1 deletion Cargo.lock

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

23 changes: 6 additions & 17 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
[package]
name = "aderyn"
version = "0.0.6"
edition = "2021"
authors = ["Alex Roan <alex@cyfrin.io>"]
description = "Rust based Solidity AST analyzer"
license = "MIT"
[workspace]
members = [
"aderyn",
"aderyn_core"
]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.4.6", features = ["derive"] }
eyre = "0.6.8"
serde = { version = "1.0.160", features = ["derive"] }
serde_json = "1.0.96"
serde_repr = "0.1.12"
tokei = "12.1.2"
toml = "0.8.2"
resolver="1"
39 changes: 32 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ cargo install aderyn

## Quick Start

The project you're running Aderyn on should be either a **Foundry** or compiled **Hardhat** project.
The root path you're running Aderyn on should be either a **Foundry** or compiled **Hardhat** project.
```sh
aderyn /path/to/your/foundry/project/root/directory/
Expand All @@ -82,25 +82,46 @@ That's it! Aderyn identifies whether the project root is a Foundry or Hardhat re

`report.md` will be output **in the directory in which you ran the command.**


### Arguments

You must provide the root directory of the repo you want to analyze.
Usage: `aderyn [OPTIONS] <ROOT>`

Options:
- `-o`, `--output <OUTPUT>`: Desired file path for the final report (will overwrite existing one) [default: report.md]
- `-h`, `--help`: Print help
- `-V`, `--version`: Print version


You must provide the root directory of the repo you want to analyze. Alternatively, you can provide a single Solidity filepath (this mode requires [Foundry](https://book.getfoundry.sh/) to be installed).

Examples:

```sh
aderyn /path/to/your/foundry/project/root/directory/
```

To run Aderyn in the folder you're currently on, run:
Run Aderyn in the folder you're currently in:
```sh
aderyn .
```
Output to a different markdown file:
```sh
aderyn -o output.md .
```
Run on a single Solidity file (requires [Foundry](https://book.getfoundry.sh/) to be installed on your machine):
```sh
aderyn src/MyContract.sol
```
## Supported Development Frameworks
Aderyn automatically detects the development framework so long as it's Foundry or Hardhat.
If the `<ROOT>` is a directory, Aderyn automatically detects the development framework so long as it's Foundry or Hardhat.

### Foundry

Expand All @@ -110,7 +131,9 @@ If Foundry is detected in the project root, Aderyn will first run `forge build`

If Hardhat is detected, Aderyn does not auto-compile. Make sure to run `hardhat compile` BEFORE running Aderyn.

## Single Solidity File Mode

If it is a Solidity file path, then Aderyn will create a temporary Foundry project, copy the contract into it, compile the contract and then analyze the AST generated by that temporary project.

# Roadmap

Expand All @@ -123,6 +146,8 @@ If Hardhat is detected, Aderyn does not auto-compile. Make sure to run `hardhat
* [ ] More complex static analysis detectors
* [ ] auto-fixes
* [ ] installer that doesn't require Rust (aderynup)
* [ ] VSCode Extension
* [ ] Python bindings
**Long-term goals - Product**
Expand All @@ -142,7 +167,7 @@ To build Aderyn locally, [install Rust](https://www.rust-lang.org/tools/install)
## Credits
This project exists thanks to all the people who [contribute](/contributing.md).<br>
This project exists thanks to all the people who [contribute](/CONTRIBUTING.md).<br>
<a href="https://github.com/cyfrin/Aderyn/graphs/contributors">
<img src="https://contrib.rocks/image?repo=cyfrin/Aderyn" />
Expand All @@ -163,4 +188,4 @@ This project exists thanks to all the people who [contribute](/contributing.md).
[issues-url]: https://github.com/cyfrin/aderyn/issues
[license-shield]: https://img.shields.io/github/license/cyfrin/aderyn?logoColor=%23fff&color=blue
[license-url]: https://github.com/cyfrin/aderyn/blob/master/LICENSE.txt
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
15 changes: 15 additions & 0 deletions aderyn/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "aderyn"
version = "0.0.7"
edition = "2021"
authors = ["Alex Roan <alex@cyfrin.io>"]
description = "Rust based Solidity AST analyzer"
license = "MIT"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
aderyn_core = { version = "0.0.7", path = "../aderyn_core" }
clap = { version = "4.4.6", features = ["derive"] }
rayon = "1.8.0"
tokei = "12.1.2"
3 changes: 3 additions & 0 deletions aderyn/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod process_foundry;
pub mod process_hardhat;
pub mod virtual_foundry;
98 changes: 98 additions & 0 deletions aderyn/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use aderyn::{process_foundry, process_hardhat, virtual_foundry};
use aderyn_core::run;
use clap::Parser;
use std::{fs::read_dir, path::PathBuf};
use tokei::{Config, LanguageType};

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Foundry or Hardhat project root directory (or path to single solidity file)
root: String,

/// Desired file path for the final report (will overwrite existing one)
#[arg(short, long, default_value = "report.md")]
output: String,
}

enum Framework {
Foundry,
Hardhat,
}

fn main() {
let args = Args::parse();

if !args.output.ends_with(".md") {
eprintln!("Warning: output file lacks the \".md\" extension in its filename.");
}

let is_single_file = args.root.ends_with(".sol") && PathBuf::from(&args.root).is_file();
let mut safe_space = PathBuf::new();

let (src_path, mut context_loader) = {
if is_single_file {
safe_space = virtual_foundry::build_isolated_workspace_for_file(&args.root);
process_foundry::with_project_root_at(&safe_space)
} else {
println!("Detecting framework...");
let root_path = PathBuf::from(&args.root);
let framework = detect_framework(root_path.clone()).unwrap_or_else(|| {
// Exit with a non-zero exit code
eprintln!("Error detecting framework");
std::process::exit(1);
});

// This whole block loads the solidity files and ASTs into the context loader
// TODO: move much of this gutsy stuff into the foundry / hardhat modules.
match framework {
Framework::Foundry => process_foundry::with_project_root_at(&root_path),
Framework::Hardhat => process_hardhat::with_project_root_at(&root_path),
}
}
};

// Using the source path, get the sloc from tokei
let mut languages = tokei::Languages::new();
let tokei_config = Config::default();
languages.get_statistics(&[src_path], &[], &tokei_config);
context_loader.set_sloc_stats(languages[&LanguageType::Solidity].clone());

// Load the context loader into the run function, which runs the detectors
run(context_loader, args.output).unwrap_or_else(|err| {
// Exit with a non-zero exit code
eprintln!("Error running aderyn");
eprintln!("{:?}", err);
std::process::exit(1);
});

if is_single_file {
virtual_foundry::delete_safe_space(&safe_space);
}
}

fn detect_framework(path: PathBuf) -> Option<Framework> {
// Canonicalize the path
let canonical_path = path.canonicalize().expect("Failed to canonicalize path");

// Check if the directory exists
if !canonical_path.is_dir() {
return None;
}

// Read the contents of the directory
let entries = read_dir(&canonical_path).expect("Failed to read directory");

for entry in entries.flatten() {
let filename = entry.file_name();
match filename.to_str() {
Some("foundry.toml") => return Some(Framework::Foundry),
Some("hardhat.config.js") | Some("hardhat.config.ts") => {
return Some(Framework::Hardhat)
}
_ => {}
}
}

None
}
87 changes: 87 additions & 0 deletions aderyn/src/process_foundry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use aderyn_core::{
context::loader::ContextLoader,
framework::foundry::{load_foundry, read_foundry_output_file},
read_file_to_string,
visitor::ast_visitor::Node,
};
use rayon::prelude::*;
use std::path::PathBuf;

pub fn with_project_root_at(root_path: &PathBuf) -> (String, ContextLoader) {
let mut context_loader = ContextLoader::default();

println!("Framework detected: Foundry mode engaged.");
println!("Foundry root path: {:?}", root_path);
let loaded_foundry = load_foundry(root_path).unwrap_or_else(|err| {
// Exit with a non-zero exit code
eprintln!("Error loading Foundry Root");
eprintln!("{:?}", err);
std::process::exit(1);
});
let src_path_buf = root_path.join(&loaded_foundry.src_path);
let src_path = src_path_buf.to_str().unwrap().to_string();
println!("Foundry src path: {:?}", src_path);

// Load the foundry output files into the context loader using the ASTs
let foundry_intermediates = loaded_foundry
.output_filepaths
.par_iter()
.map(|output_filepath| {
if let Ok(foundry_output) = read_foundry_output_file(output_filepath.to_str().unwrap())
{
Some(foundry_output.ast)
} else {
eprintln!(
"Error reading Foundry output file: {}",
output_filepath.to_str().unwrap()
);
None
}
})
.collect::<Vec<_>>();

// read_foundry_output_file and print an error message if it fails
foundry_intermediates.into_iter().flatten().for_each(|ast| {
ast.accept(&mut context_loader).unwrap_or_else(|err| {
// Exit with a non-zero exit code
eprintln!("Error loading Foundry AST into ContextLoader");
eprintln!("{:?}", err);
std::process::exit(1);
})
});

// Load the solidity source files into memory, and assign the content to the source_unit.source
for source_filepath in loaded_foundry.src_filepaths {
match read_file_to_string(&source_filepath) {
Ok(content) => {
// Convert the full_path to a string
let full_path_str = source_filepath.to_str().unwrap_or("");

// Find the index where "src/" starts
let src_component = src_path_buf.file_name().unwrap().to_str().unwrap();
if let Some(start_index) = full_path_str.find(src_component) {
let target_path = &full_path_str[start_index..];

// Search for a match and modify
for unit in &context_loader.source_units {
if let Some(ref abs_path) = unit.absolute_path {
if abs_path == target_path {
context_loader.set_source_unit_source_content(unit.id, content);
break;
}
}
}
}
}
Err(err) => {
eprintln!(
"Error reading Solidity source file: {}",
source_filepath.to_str().unwrap()
);
eprintln!("{:?}", err);
}
}
}

(src_path, context_loader)
}
Loading

0 comments on commit d824cff

Please sign in to comment.