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
10 changes: 9 additions & 1 deletion bin/pcl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ use pcl_core::{
ConfigArgs,
},
};
use pcl_phoundry::phorge::PhorgeTest;
use pcl_phoundry::{
build::BuildArgs,
phorge_test::PhorgeTest,
};
use serde_json::json;

const VERSION_MESSAGE: &str = concat!(
Expand Down Expand Up @@ -50,6 +53,8 @@ enum Commands {
Auth(AuthCommand),
#[command(about = "Manage configuration")]
Config(ConfigArgs),
#[command(name = "build")]
Build(BuildArgs),
}

#[tokio::main]
Expand Down Expand Up @@ -85,6 +90,9 @@ async fn main() -> Result<()> {
Commands::Config(config_cmd) => {
config_cmd.run(&mut config)?;
}
Commands::Build(build_cmd) => {
build_cmd.run()?;
}
};
config.write_to_file(&cli.args)?;
Ok::<_, Report>(())
Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/assertion_da.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use indicatif::{
ProgressStyle,
};
use pcl_common::args::CliArgs;
use pcl_phoundry::phorge::{
use pcl_phoundry::build_and_flatten::{
BuildAndFlatOutput,
BuildAndFlattenArgs,
};
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/common/da_store_harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use pcl_core::{
assertion_da::DaStoreArgs,
error::DaSubmitError,
};
use pcl_phoundry::phorge::BuildAndFlattenArgs;
use pcl_phoundry::build_and_flatten::BuildAndFlattenArgs;
use std::{
collections::HashMap,
path::PathBuf,
Expand Down
222 changes: 222 additions & 0 deletions crates/phoundry/src/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
use clap::{
Parser,
ValueHint,
};
use foundry_cli::opts::{
BuildOpts,
ProjectPathOpts,
};

use std::path::PathBuf;

use crate::compile::compile;
use crate::error::PhoundryError;

/// Command-line arguments for building assertion contracts and tests.
#[derive(Debug, Default, Parser)]
#[clap(about = "Build contracts using Phorge")]
pub struct BuildArgs {
/// Root directory of the project
#[clap(
short = 'r',
long,
value_hint = ValueHint::DirPath,
help = "Root directory of the project"
)]
pub root: Option<PathBuf>,
}

impl BuildArgs {
/// Builds the assertion contract and tests
///
/// # Returns
///
/// - `Ok(())`
/// - `Err(PhoundryError)` if any step in the process fails
pub fn run(&self) -> Result<(), Box<PhoundryError>> {
let build_cmd = BuildOpts {
project_paths: ProjectPathOpts {
root: self.root.clone(),
// FIXME(Odysseas): this essentially hard-codes the location of the assertions to live in
// assertions/src
contracts: Some(PathBuf::from("assertions/src")),
..Default::default()
},
..Default::default()
};

foundry_cli::utils::load_dotenv();

compile(build_cmd)?;
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;

// Helper function to create a temporary Solidity project with valid contracts
fn setup_valid_test_project() -> (TempDir, PathBuf) {
let temp_dir = TempDir::new().unwrap();
let project_root = temp_dir.path().join("test_project");
fs::create_dir_all(&project_root).unwrap();

// Create assertions/src directory structure
let contract_dir = project_root.join("assertions").join("src");
fs::create_dir_all(&contract_dir).unwrap();

// Create a valid test contract
let contract_path = contract_dir.join("ValidContract.sol");
fs::write(
&contract_path,
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ValidContract {
function test() public pure returns (bool) {
return true;
}
}"#,
)
.unwrap();

(temp_dir, project_root)
}

// Helper function to create a temporary Solidity project with compilation errors
fn setup_invalid_test_project() -> (TempDir, PathBuf) {
let temp_dir = TempDir::new().unwrap();
let project_root = temp_dir.path().join("test_project");
fs::create_dir_all(&project_root).unwrap();

// Create assertions/src directory structure
let contract_dir = project_root.join("assertions").join("src");
fs::create_dir_all(&contract_dir).unwrap();

// Create a contract with compilation errors
let contract_path = contract_dir.join("InvalidContract.sol");
fs::write(
&contract_path,
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract InvalidContract {
// Missing semicolon - syntax error
uint256 public value = 42

// Invalid function syntax - missing parentheses
function test public pure returns (bool) {
// Type mismatch error
return "not a boolean";
}

// Undefined variable error
function anotherTest() public pure returns (uint256) {
return undefinedVariable;
}

// Missing closing brace
}"#,
)
.unwrap();

(temp_dir, project_root)
}

// Helper function to create an empty project (no source files)
fn setup_empty_test_project() -> (TempDir, PathBuf) {
let temp_dir = TempDir::new().unwrap();
let project_root = temp_dir.path().join("test_project");
fs::create_dir_all(&project_root).unwrap();

// Create assertions/src directory but leave it empty
let contract_dir = project_root.join("assertions").join("src");
fs::create_dir_all(&contract_dir).unwrap();

(temp_dir, project_root)
}

#[test]
fn test_build_args_new() {
let args = BuildArgs { root: None };

assert!(args.root.is_none());
}

#[test]
fn test_build_args_with_root() {
let root_path = PathBuf::from("/test/path");
let args = BuildArgs {
root: Some(root_path.clone()),
};

assert_eq!(args.root, Some(root_path));
}

#[test]
fn test_compilation_with_invalid_contract() {
let (_temp_dir, project_root) = setup_invalid_test_project();

let args = BuildArgs {
root: Some(project_root),
};

let result = args.run();

// Compilation should fail due to syntax errors
assert!(
result.is_err(),
"Expected compilation to fail with invalid contract"
);
}

#[test]
fn test_compilation_with_empty_directory() {
let (_temp_dir, project_root) = setup_empty_test_project();

let args = BuildArgs {
root: Some(project_root),
};

let result = args.run();

// Compilation should fail due to no source files
assert!(
result.is_err(),
"Expected compilation to fail with empty directory"
);
}

#[test]
fn test_compilation_with_nonexistent_directory() {
let temp_dir = TempDir::new().unwrap();
let nonexistent_path = temp_dir.path().join("nonexistent_project");

let args = BuildArgs {
root: Some(nonexistent_path),
};

let result = args.run();

assert!(
result.is_err(),
"Expected compilation to fail with nonexistent directory"
);
}

#[tokio::test(flavor = "multi_thread")]
async fn test_build_integration_with_valid_contract() {
let (_temp_dir, project_root) = setup_valid_test_project();

let args = BuildArgs {
root: Some(project_root),
};

let result = args.run();

assert!(result.is_ok());
}
}
Loading
Loading