Skip to content

Commit

Permalink
Add initial support for foundry projects
Browse files Browse the repository at this point in the history
  • Loading branch information
camden-smallwood committed Jun 17, 2023
1 parent b01c62e commit d566565
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 35 deletions.
8 changes: 8 additions & 0 deletions solast/src/foundry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};
use solidity::ast::*;

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct File {
pub ast: SourceUnit,
}
71 changes: 51 additions & 20 deletions solast/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
mod analysis;
mod brownie;
mod foundry;
mod hardhat;
mod report;
mod truffle;
mod todo_list;
mod truffle;

use report::Report;
use solidity::ast::*;
Expand Down Expand Up @@ -34,9 +35,9 @@ fn main() -> io::Result<()> {
let mut project_path: Option<PathBuf> = None;
let mut should_print_todo_list = false;
let mut visitor_names: HashSet<String> = HashSet::new();
let mut contract_name: Option<String> = None;
let mut contract_names: Vec<String> = vec![];
let mut contract_paths: Vec<PathBuf> = vec![];
let mut output_format = OutputFormat::PlainText;
let mut contract_paths = vec![];

for arg in args {
match arg {
Expand All @@ -46,11 +47,7 @@ fn main() -> io::Result<()> {
}

s if s.starts_with("contract=") => {
if contract_name.is_some() {
return Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Multiple contracts specified: {} {}", project_path.unwrap().to_string_lossy(), arg)));
}

contract_name = Some(s.trim_start_matches("contract=").into());
contract_names.push(s.trim_start_matches("contract=").into());
}

s if s.starts_with("contract-path=") || s.starts_with("contract_path=") => {
Expand Down Expand Up @@ -130,6 +127,7 @@ fn main() -> io::Result<()> {
let hardhat_config_js_path = project_path.join("hardhat.config.js");
let hardhat_config_ts_path = project_path.join("hardhat.config.ts");
let truffle_config_path = project_path.join("truffle-config.js");
let foundry_config_path = project_path.join("foundry.toml");

if brownie_config_path.is_file() {
//
Expand All @@ -156,10 +154,8 @@ fn main() -> io::Result<()> {
let file: brownie::File = simd_json::from_reader(File::open(path)?)?;

if let Some(mut source_unit) = file.ast {
if let Some(contract_name) = contract_name.as_deref() {
if !source_unit.contract_definitions().iter().any(|c| c.name == contract_name) {
continue;
}
if !contract_names.is_empty() && !contract_names.iter().any(|contract_name| source_unit.contract_definitions().iter().any(|c| c.name == *contract_name)) {
continue;
}

if !source_units.iter().any(|existing_source_unit| existing_source_unit.absolute_path == source_unit.absolute_path) {
Expand Down Expand Up @@ -198,10 +194,8 @@ fn main() -> io::Result<()> {
continue;
}

if let Some(contract_name) = contract_name.as_deref() {
if !source_unit.contract_definitions().iter().any(|c| c.name == contract_name) {
continue;
}
if !contract_names.is_empty() && !contract_names.iter().any(|contract_name| source_unit.contract_definitions().iter().any(|c| c.name == *contract_name)) {
continue;
}

if !source_units.iter().any(|existing_source_unit| existing_source_unit.absolute_path == source_unit.absolute_path) {
Expand Down Expand Up @@ -239,10 +233,8 @@ fn main() -> io::Result<()> {
continue;
}

if let Some(contract_name) = contract_name.as_deref() {
if !source_unit.contract_definitions().iter().any(|c| c.name == contract_name) {
continue;
}
if !contract_names.is_empty() && !contract_names.iter().any(|contract_name| source_unit.contract_definitions().iter().any(|c| c.name == *contract_name)) {
continue;
}

if !source_units.iter().any(|existing_source_unit| existing_source_unit.absolute_path == source_unit.absolute_path) {
Expand All @@ -251,6 +243,45 @@ fn main() -> io::Result<()> {
}
}
}
} else if foundry_config_path.is_file() {
//
// TODO:
// * load build_path from `foundry.toml`
// * ignore contracts under lib paths from `foundry.toml`
//

let build_path = project_path.join("out");

if !build_path.exists() || !build_path.is_dir() {
todo!("foundry project not compiled")
}

for path in std::fs::read_dir(build_path)? {
let path = path?.path();

if !path.is_dir() {
continue;
}

for path in std::fs::read_dir(path)? {
let path = path?.path();

if !path.is_file() || !path.extension().map(|extension| extension == "json").unwrap_or(false) {
continue;
}

let mut file: foundry::File = simd_json::from_reader(File::open(path)?)?;

if !contract_names.is_empty() && !contract_names.iter().any(|contract_name| file.ast.contract_definitions().iter().any(|c| c.name == *contract_name)) {
continue;
}

if !source_units.iter().any(|existing_source_unit| existing_source_unit.absolute_path == file.ast.absolute_path) {
file.ast.source = Some(std::fs::read_to_string(project_path.join(file.ast.absolute_path.clone().unwrap()))?);
source_units.push(file.ast);
}
}
}
} else {
unimplemented!("No supported project configuration found")
}
Expand Down
2 changes: 1 addition & 1 deletion solidity/src/ast/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1519,7 +1519,7 @@ impl AstBuilder {
) -> IndexAccess {
IndexAccess {
base_expression: Box::new(self.build_expression(array)),
index_expression: Box::new(self.build_expression(index)),
index_expression: Some(Box::new(self.build_expression(index))),
argument_types: None, // TODO
is_constant: false, // TODO
is_l_value: false, // TODO
Expand Down
14 changes: 11 additions & 3 deletions solidity/src/ast/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ pub struct FunctionCallOptionsContext<'a, 'b> {
#[serde(rename_all = "camelCase")]
pub struct IndexAccess {
pub base_expression: Box<Expression>,
pub index_expression: Box<Expression>,
pub index_expression: Option<Box<Expression>>,
pub argument_types: Option<Vec<TypeDescriptions>>,
pub is_constant: bool,
pub is_l_value: bool,
Expand All @@ -533,13 +533,21 @@ pub struct IndexAccess {

impl IndexAccess {
pub fn contains_operation(&self, operator: &str) -> bool {
self.index_expression.contains_operation(operator)
self.index_expression.as_ref().map(|x| x.contains_operation(operator)).unwrap_or(false)
}
}

impl Display for IndexAccess {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}[{}]", self.base_expression, self.index_expression))
write!(
f,
"{}[{}]",
self.base_expression,
match self.index_expression.as_ref() {
Some(x) => format!("{x}"),
None => String::new(),
}
)
}
}

Expand Down
3 changes: 3 additions & 0 deletions solidity/src/ast/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum Statement {
Return(Return),
RevertStatement(RevertStatement),
ExpressionStatement(ExpressionStatement),
Block(Block),
InlineAssembly(InlineAssembly),
Continue {
src: String,
Expand Down Expand Up @@ -50,6 +51,7 @@ impl<'de> Deserialize<'de> for Statement {
"Return" => Ok(Statement::Return(serde_json::from_value(json).unwrap())),
"RevertStatement" => Ok(Statement::RevertStatement(serde_json::from_value(json).unwrap())),
"ExpressionStatement" => Ok(Statement::ExpressionStatement(serde_json::from_value(json).unwrap())),
"Block" => Ok(Statement::Block(serde_json::from_value(json).unwrap())),
"InlineAssembly" => Ok(Statement::InlineAssembly(serde_json::from_value(json).unwrap())),
"Continue" => Ok(Statement::Continue {
src: json.get("src").unwrap().as_str().unwrap().to_string(),
Expand Down Expand Up @@ -88,6 +90,7 @@ impl Display for Statement {
Statement::UncheckedBlock(stmt) => stmt.fmt(f),
Statement::Return(stmt) => stmt.fmt(f),
Statement::ExpressionStatement(stmt) => stmt.fmt(f),
Statement::Block(stmt) => stmt.fmt(f),
Statement::InlineAssembly(_) => write!(f, "assembly {{ /* WARNING: not implemented */ }}"),
Statement::Continue { .. } => write!(f, "continue"),
Statement::Break { .. } => write!(f, "break"),
Expand Down
38 changes: 27 additions & 11 deletions solidity/src/ast/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,20 @@ impl AstVisitor for AstVisitorData<'_> {
self.leave_expression(&mut context)?;
}

Statement::Block(block) => {
let mut context = BlockContext {
source_units: context.source_units,
current_source_unit: context.current_source_unit,
contract_definition: context.contract_definition,
definition_node: context.definition_node,
blocks: context.blocks,
block,
};

self.visit_block(&mut context)?;
self.leave_block(&mut context)?;
}

Statement::InlineAssembly(inline_assembly) => {
let mut context = InlineAssemblyContext {
source_units: context.source_units,
Expand Down Expand Up @@ -1987,18 +2001,20 @@ impl AstVisitor for AstVisitorData<'_> {
self.visit_expression(&mut base_context)?;
self.leave_expression(&mut base_context)?;

let mut index_context = ExpressionContext {
source_units: context.source_units,
current_source_unit: context.current_source_unit,
contract_definition: context.contract_definition,
definition_node: context.definition_node,
blocks: context.blocks,
statement: context.statement,
expression: context.index_access.index_expression.as_ref(),
};
if let Some(expression) = context.index_access.index_expression.as_ref() {
let mut index_context = ExpressionContext {
source_units: context.source_units,
current_source_unit: context.current_source_unit,
contract_definition: context.contract_definition,
definition_node: context.definition_node,
blocks: context.blocks,
statement: context.statement,
expression,
};

self.visit_expression(&mut index_context)?;
self.leave_expression(&mut index_context)?;
self.visit_expression(&mut index_context)?;
self.leave_expression(&mut index_context)?;
}

Ok(())
}
Expand Down

0 comments on commit d566565

Please sign in to comment.