Skip to content
This repository has been archived by the owner on Jul 3, 2024. It is now read-only.

Commit

Permalink
refactor(libs/foundry-wrapper): rework of foundry wrapper library to …
Browse files Browse the repository at this point in the history
…handle foundry as external executable
  • Loading branch information
0xmemorygrinder committed Dec 6, 2023
1 parent 258a578 commit e43be75
Show file tree
Hide file tree
Showing 13 changed files with 382 additions and 7,607 deletions.
6,990 changes: 173 additions & 6,817 deletions libs/foundry-wrapper/Cargo.lock

Large diffs are not rendered by default.

42 changes: 4 additions & 38 deletions libs/foundry-wrapper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
glob = "0.3.1"
clap = "4.4.8"
thiserror = "1.0"
foundry-config = { git = "https://github.com/foundry-rs/foundry" }
forge = { git = "https://github.com/foundry-rs/foundry" }
foundry-cli = { git = "https://github.com/foundry-rs/foundry" }
foundry-common = { git = "https://github.com/foundry-rs/foundry" }
# solc & compilation utilities
foundry-compilers = { version = "0.1", default-features = false }

# forge dependencies
eyre = "0.6"
once_cell = "1"
semver = "1"
tracing = "0.1"
regex = { version = "1", default-features = false }
yansi = "0.5"

[patch.crates-io]
ethers = { git = "https://github.com/gakonst/ethers-rs", rev = "546ea029362a7502365667c6f99fac92ad0aeb9f" }
ethers-core = { git = "https://github.com/gakonst/ethers-rs", rev = "546ea029362a7502365667c6f99fac92ad0aeb9f" }
ethers-contract = { git = "https://github.com/gakonst/ethers-rs", rev = "546ea029362a7502365667c6f99fac92ad0aeb9f" }
ethers-contract-abigen = { git = "https://github.com/gakonst/ethers-rs", rev = "546ea029362a7502365667c6f99fac92ad0aeb9f" }
ethers-providers = { git = "https://github.com/gakonst/ethers-rs", rev = "546ea029362a7502365667c6f99fac92ad0aeb9f" }
ethers-signers = { git = "https://github.com/gakonst/ethers-rs", rev = "546ea029362a7502365667c6f99fac92ad0aeb9f" }
ethers-middleware = { git = "https://github.com/gakonst/ethers-rs", rev = "546ea029362a7502365667c6f99fac92ad0aeb9f" }
ethers-solc = { git = "https://github.com/gakonst/ethers-rs", rev = "546ea029362a7502365667c6f99fac92ad0aeb9f" }

foundry-block-explorers = { git = "https://github.com/foundry-rs/block-explorers" }

alloy-dyn-abi = { git = "https://github.com/alloy-rs/core/" }
alloy-json-abi = { git = "https://github.com/alloy-rs/core/" }
alloy-primitives = { git = "https://github.com/alloy-rs/core/" }
alloy-sol-macro = { git = "https://github.com/alloy-rs/core/" }
alloy-sol-types = { git = "https://github.com/alloy-rs/core/" }
alloy-sol-type-parser = { git = "https://github.com/alloy-rs/core/" }
syn-solidity = { git = "https://github.com/alloy-rs/core/" }

revm = { git = "https://github.com/bluealloy/revm", rev = "1609e07c68048909ad1682c98cf2b9baa76310b5" }
revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "1609e07c68048909ad1682c98cf2b9baa76310b5" }
thiserror = "1.0.50"
which = "5.0"
serde = "1.0.193"
serde_json = "1.0.108"
80 changes: 41 additions & 39 deletions libs/foundry-wrapper/src/compiler.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use crate::{types::{WorkspaceConfig, ProjectCompileOutput}, error::Error, utils::install_missing_dependencies};
use std::process::Command;
use serde_json;
use crate::{types::ProjectCompileOutput, error::Error, utils::{find_projects_paths, find_forge_executable, check_executable_argument}};

#[derive(Debug)]
pub struct CompilerInner {
struct CompilerInner {
root_path: String,
workspace_config: WorkspaceConfig,
workspaces: Vec<String>,
executable_path: String,
}

#[derive(Debug)]
Expand All @@ -12,54 +15,53 @@ pub struct Compiler {
}

impl Compiler {
pub fn new() -> Self {
Self {
pub fn new_with_executable_check() -> Result<Self, Error> {
let executable_path = find_forge_executable()?;
check_executable_argument(executable_path.to_str().unwrap_or_default())?;
Ok(Self {
inner: CompilerInner {
root_path: String::new(),
workspace_config: WorkspaceConfig::new(),
workspaces: Vec::new(),
executable_path: executable_path.to_str().unwrap_or_default().to_string(),
}
}
})
}

fn find_closest_workspace(&self, file_path: &str) -> Option<String> {
self.inner.workspaces
.iter()
.filter(|path| file_path.starts_with(path.as_str()))
.max_by_key(|path| path.len())
.map(|path| path.to_string())
}

pub fn load_workspace(&mut self, root_folder: String) -> Result<(), Error> {
self.inner.workspace_config.load_projects(&root_folder)?;
let paths = find_projects_paths(&root_folder)?;
for path in paths {
if let Some(path) = path.to_str() {
self.inner.workspaces.push(path.to_string());
}
}
self.inner.root_path = root_folder;
Ok(())
}

pub fn reload_project_for_file(&mut self, file_path: &str) -> Result<(), Error> {
self.inner.workspace_config.reload_project_for_file(file_path)
pub fn reload_project_for_file(&mut self, _: &str) -> Result<(), Error> {
Ok(())
}

pub fn compile(&mut self, file_path: &str) -> Result<(String, ProjectCompileOutput), Error> {
let (_, config) = self.inner.workspace_config.get_config_for_file(file_path)
.ok_or_else(|| Error::InvalidFilePath(file_path.to_string()))?;

if install_missing_dependencies(config, true) {
let path = self.inner.root_path.clone();
self.inner.workspace_config.reload_project_for_file(&path)?;
}
let (project_path, project) = self.inner.workspace_config.get_project_for_file(file_path)
.ok_or_else(|| Error::InvalidFilePath(file_path.to_string()))?;

Ok((project_path, project.compile()?))

/*
if install::install_missing_dependencies(&mut config, self.args.silent) &&
config.auto_detect_remappings
{
// need to re-configure here to also catch additional remappings
config = self.load_config();
project = config.project()?;
}
let filters = self.skip.unwrap_or_default();
if self.args.silent {
compile::suppress_compile_with_filter(&project, filters)
} else {
let compiler = ProjectCompiler::with_filter(self.names, self.sizes, filters);
compiler.compile(&project)
} */
let workspace_path = self.find_closest_workspace(&file_path).ok_or_else(|| Error::InvalidFilePath(file_path.to_string()))?;
let json = Command::new(&self.inner.executable_path)
.current_dir(&workspace_path)
.arg("compile")
.arg("--format-json")
.output()
.map_err(|e| {
Error::ExecutableError(e)
})?;
let output_str = String::from_utf8_lossy(&json.stdout);
let compile_output: ProjectCompileOutput = serde_json::from_str(&output_str)?;
return Ok((workspace_path, compile_output));
}
}
26 changes: 17 additions & 9 deletions libs/foundry-wrapper/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
use foundry_compilers::error::SolcError;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
#[error("Config loading error: {0}")]
ConfigLoading(SolcError),
#[error("Project loading error: {0}")]
ProjectLoading(SolcError),

#[error("Workspace loading error: {0}")]
InvalidRootPath(#[from] glob::PatternError),

#[error("Invalid file path: {0}")]
InvalidFilePath(String),
#[error("Compilation error: {0}")]
CompilationError(SolcError),
#[error("Unkown error: {0}")]
UnkownError(#[from] SolcError),

#[error("Executable error: foundry executable not found")]
FoundryExecutableNotFound,

#[error("Invalid foundry version: does not support --format-json")]
InvalidFoundryVersion,

#[error("Executable error: {0}")]
ExecutableError(std::io::Error),

#[error("No executable build-info file: {0}")]
NoExecutableBuildInfoFile(String),

#[error("Invalid json output: {0}")]
InvalidJsonOutput(#[from] serde_json::Error),
}
127 changes: 119 additions & 8 deletions libs/foundry-wrapper/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,122 @@
pub(crate) mod project_compile_output;
pub use project_compile_output::{ProjectCompileOutput, CompilationError, Position, Range, Severity};
use serde::{Deserialize, Serialize};

pub(crate) mod project_config;
pub use project_config::ProjectConfig;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProjectCompileOutput {
errors: Vec<CompilationError>
}

pub(crate) mod project;
pub use project::Project;
impl ProjectCompileOutput {
pub fn get_errors(&self) -> &Vec<CompilationError> {
self.errors.as_ref()
}
}

pub(crate) mod workspace_config;
pub use workspace_config::WorkspaceConfig;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CompilationError {
#[serde(rename = "sourceLocation")]
source_location: Option<SourceLocation>,
#[serde(rename = "type")]
typ: String,
component: String,
severity: String,
#[serde(rename = "errorCode")]
error_code: String,
message: String,
#[serde(rename = "formattedMessage")]
formatted_message: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
struct SourceLocation {
file: String,
start: i32,
end: i32,
}

impl CompilationError {
pub fn get_message(&self) -> String {
self.message.clone()
}

pub fn get_file_path(&self) -> Option<String> {
Some(self.source_location.clone()?.file.clone())
}

pub fn get_start_idx(&self) -> Option<i32> {
Some(self.source_location.clone()?.start)
}

pub fn get_end_idx(&self) -> Option<i32> {
Some(self.source_location.clone()?.end)
}

pub fn get_start_position(&self, source_content: &str) -> Option<Position> {
let idx = self.get_start_idx()?;
Position::from_index(idx, source_content)
}

pub fn get_end_position(&self, source_content: &str) -> Option<Position> {
let idx = self.get_end_idx()?;
Position::from_index(idx, source_content)
}

pub fn get_range(&self, source_content: &str) -> Option<Range> {
Some(Range {
start: self.get_start_position(source_content)?,
end: self.get_end_position(source_content)?
})
}

pub fn get_severity(&self) -> Severity {
self.severity.clone().into()
}
}

/**
* Position of error, 0 based indexes
*/
#[derive(Clone, Debug)]
pub struct Position {
pub line: u32,
pub column: u32
}

impl Position {
pub fn from_index(idx: i32, source: &str) -> Option<Self> {
let mut idx: usize = idx as usize;
for (i, l) in source.lines().enumerate() {
if idx < l.len() {
return Some(Self {
line: i as u32,
column: idx as u32
})
}
idx -= l.len() + 1;
}
None
}
}

#[derive(Clone, Debug)]
pub struct Range {
pub start: Position,
pub end: Position
}

#[derive(Clone, Debug)]
pub enum Severity {
Error,
Warning,
Info,
}

impl From<String> for Severity {
fn from(severity: String) -> Self {
match severity {
s if s.to_uppercase() == "ERROR" => Self::Error,
s if s.to_uppercase() == "WARNING" => Self::Warning,
s if s.to_uppercase() == "INFO" => Self::Info,
_ => Self::Info
}
}
}
25 changes: 0 additions & 25 deletions libs/foundry-wrapper/src/types/project.rs

This file was deleted.

Loading

0 comments on commit e43be75

Please sign in to comment.