Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support cairo project #47

Merged
merged 2 commits into from
Sep 20, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ Run printers:
```bash
caracal print path/file/to/analyze --printer printer_to_use --corelib path/to/corelib/src
```
### Cairo project
If you have a cairo project with multiple files and contracts you may need to specify which contract with `--contract-path`. The local cairo compiler binary is used if available otherwise a bundled compiler is used. In the latter case you also need to specify the corelib as explained above for the standalone case. The path is the directory where `cairo_project.toml` resides.
Run detectors:
```bash
caracal detect path/to/dir
```
```bash
caracal detect path/to/dir --contract-path token::myerc20::...
```
Run printers:
```bash
caracal print path/to/dir --printer printer_to_use
```
### Scarb
If you have a project that uses Scarb you need to add the following in Scarb.toml:
```bash
Expand Down
5 changes: 5 additions & 0 deletions src/cli/commands/detect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ pub struct DetectArgs {
#[arg(long)]
corelib: Option<PathBuf>,

/// Path to the contract to compile when using a cairo project with multiple contracts
#[arg(long)]
contract_path: Option<String>,

/// Detectors to run
#[arg(long, num_args(0..), conflicts_with_all(["exclude", "exclude_informational", "exclude_low", "exclude_medium", "exclude_high"]))]
detect: Option<Vec<String>>,
Expand Down Expand Up @@ -48,6 +52,7 @@ impl From<&DetectArgs> for CoreOpts {
CoreOpts {
target: args.target.clone(),
corelib: args.corelib.clone(),
contract_path: args.contract_path.clone(),
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/cli/commands/print/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ pub struct PrintArgs {
#[arg(long)]
corelib: Option<PathBuf>,

/// Path to the contract to compile when using a cairo project with multiple contracts
#[arg(long)]
contract_path: Option<String>,

/// Which functions to run the printer (all, user-functions)
#[arg(short, long, default_value_t = Filter::UserFunctions)]
filter: Filter,
Expand All @@ -28,6 +32,7 @@ impl From<&PrintArgs> for CoreOpts {
CoreOpts {
target: args.target.clone(),
corelib: args.corelib.clone(),
contract_path: args.contract_path.clone(),
}
}
}
Expand Down
137 changes: 137 additions & 0 deletions src/compilation/cairo_project.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use anyhow::{anyhow, bail, Context, Result};
use std::env;
use std::process;
use std::sync::Arc;

use super::ProgramCompiled;
use crate::compilation::utils::felt252_serde::sierra_from_felt252s;
use crate::compilation::utils::replacer::SierraProgramDebugReplacer;
use crate::core::core_unit::CoreOpts;
use cairo_lang_compiler::db::RootDatabase;
use cairo_lang_compiler::project::{setup_project, ProjectConfig, ProjectConfigContent};
use cairo_lang_compiler::CompilerConfig;
use cairo_lang_filesystem::ids::Directory;
use cairo_lang_sierra_generator::db::SierraGenGroup;
use cairo_lang_sierra_generator::replace_ids::{replace_sierra_ids_in_program, SierraIdReplacer};
use cairo_lang_starknet::abi::{AbiBuilder, Contract};
use cairo_lang_starknet::compiler_version::current_compiler_version_id;
use cairo_lang_starknet::contract::find_contracts;
use cairo_lang_starknet::contract_class::ContractClass;
use cairo_lang_starknet::inline_macros::selector::SelectorMacro;
use cairo_lang_starknet::plugin::StarkNetPlugin;
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;

pub fn compile(opts: CoreOpts) -> Result<Vec<ProgramCompiled>> {
let output = process::Command::new("starknet-compile")
.arg("--version")
.output();

if let Ok(result) = output {
if result.status.success() {
println!(
"Found local cairo installation {}",
String::from_utf8(result.stdout)?
);

return local_compiler(opts);
}
}

println!(
"Local cairo installation not found. Compiling with starknet-compile {}",
current_compiler_version_id()
);

// corelib cli option has priority over the environment variable
let corelib = match opts.corelib {
Some(ref p) => p.clone(),
None => {
match env::var("CORELIB_PATH") {
Ok(p) => p.into(),
Err(e) => bail!("{e}. The Corelib path must be specified either with the CORELIB_PATH environment variable or the --corelib cli option"),
}
}
};

// Needed to pass the correct corelib path
let project_config = ProjectConfig {
corelib: Some(Directory::Real(corelib)),
base_path: "".into(),
content: ProjectConfigContent {
crate_roots: OrderedHashMap::default(),
},
};

let mut db = RootDatabase::builder()
.with_project_config(project_config)
.with_macro_plugin(Arc::new(StarkNetPlugin::default()))
.with_inline_macro_plugin(SelectorMacro::NAME, Arc::new(SelectorMacro))
.build()?;

let mut compiler_config = CompilerConfig::default();

let main_crate_ids = setup_project(&mut db, &opts.target)?;
compiler_config.diagnostics_reporter.ensure(&db)?;

let contracts = find_contracts(&db, &main_crate_ids);
if contracts.is_empty() {
bail!("Contract not found.");
}

let mut abi: Contract = Default::default();
contracts.iter().for_each(|c| {
abi.items.extend(
AbiBuilder::submodule_as_contract_abi(&db, c.submodule_id)
.expect("Error when getting the ABI.")
.items,
)
});

let sierra = db
.get_sierra_program(main_crate_ids)
.ok()
.context("Compilation failed without any diagnostics.")?;
let sierra = replace_sierra_ids_in_program(&db, &sierra);

Ok(vec![ProgramCompiled { sierra, abi }])
}

fn local_compiler(opts: CoreOpts) -> Result<Vec<ProgramCompiled>> {
let output = if let Some(contract_path) = opts.contract_path {
process::Command::new("starknet-compile")
.arg(opts.target)
.arg("--contract-path")
.arg(contract_path)
.arg("--replace-ids")
.output()?
} else {
process::Command::new("starknet-compile")
.arg(opts.target)
.arg("--replace-ids")
.output()?
};

if !output.status.success() {
bail!(anyhow!(
"starknet-compile failed to compile.\n Status {}\n {}",
output.status,
String::from_utf8(output.stderr)?
));
}

let contract_class: ContractClass =
serde_json::from_str(&String::from_utf8(output.stdout)?).unwrap();

// We don't have to check the existence because we ran the compiler with --replace-ids
let debug_info = contract_class.sierra_program_debug_info.unwrap();

let sierra = sierra_from_felt252s(&contract_class.sierra_program)
.unwrap()
.2;
let sierra = SierraProgramDebugReplacer { debug_info }.apply(&sierra);

Ok(vec![ProgramCompiled {
sierra,
abi: contract_class.abi.unwrap(),
}])
}
6 changes: 5 additions & 1 deletion src/compilation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use cairo_lang_starknet::abi::Contract;

use crate::core::core_unit::CoreOpts;

mod cairo_project;
mod scarb;
mod standard;
mod utils;
Expand All @@ -20,8 +21,11 @@ pub fn compile(opts: CoreOpts) -> Result<Vec<ProgramCompiled>> {
if let Ok(entries) = fs::read_dir(opts.target.as_path()) {
for entry in entries.flatten() {
if entry.file_name() == "Scarb.toml" {
println!("Compiling with Scarb.");
println!("Compiling with Scarb. Found Scarb.toml.");
return scarb::compile(opts);
} else if entry.file_name() == "cairo_project.toml" {
println!("Compiling with Cairo. Found cairo_project.toml.");
return cairo_project::compile(opts);
}
}
}
Expand Down
55 changes: 2 additions & 53 deletions src/compilation/scarb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use std::path::Path;
use std::process;

use crate::compilation::utils::felt252_serde::sierra_from_felt252s;
use crate::compilation::utils::replacer::SierraProgramDebugReplacer;
use crate::compilation::ProgramCompiled;
use crate::core::core_unit::CoreOpts;
use cairo_lang_sierra::debug_info::DebugInfo;
use cairo_lang_sierra_generator::replace_ids::SierraIdReplacer;
use cairo_lang_starknet::contract_class::ContractClass;

Expand Down Expand Up @@ -81,7 +81,7 @@ pub fn compile(opts: CoreOpts) -> Result<Vec<ProgramCompiled>> {
let program = sierra_from_felt252s(&contract_class.sierra_program)
.unwrap()
.2;
let program = ScarbDebugReplacer { debug_info }.apply(&program);
let program = SierraProgramDebugReplacer { debug_info }.apply(&program);
programs_compiled.push(ProgramCompiled {
sierra: program,
abi: contract_class.abi.unwrap(),
Expand All @@ -90,54 +90,3 @@ pub fn compile(opts: CoreOpts) -> Result<Vec<ProgramCompiled>> {

Ok(programs_compiled)
}

struct ScarbDebugReplacer {
debug_info: DebugInfo,
}

impl SierraIdReplacer for ScarbDebugReplacer {
fn replace_libfunc_id(
&self,
id: &cairo_lang_sierra::ids::ConcreteLibfuncId,
) -> cairo_lang_sierra::ids::ConcreteLibfuncId {
let func_name = self
.debug_info
.libfunc_names
.get(id)
.expect("No libfunc in debug info");
cairo_lang_sierra::ids::ConcreteLibfuncId {
id: id.id,
debug_name: Some(func_name.clone()),
}
}

fn replace_type_id(
&self,
id: &cairo_lang_sierra::ids::ConcreteTypeId,
) -> cairo_lang_sierra::ids::ConcreteTypeId {
let type_name = self
.debug_info
.type_names
.get(id)
.expect("No typeid in debug info");
cairo_lang_sierra::ids::ConcreteTypeId {
id: id.id,
debug_name: Some(type_name.clone()),
}
}

fn replace_function_id(
&self,
sierra_id: &cairo_lang_sierra::ids::FunctionId,
) -> cairo_lang_sierra::ids::FunctionId {
let function_name = self
.debug_info
.user_func_names
.get(sierra_id)
.expect("No funcid in debug info");
cairo_lang_sierra::ids::FunctionId {
id: sierra_id.id,
debug_name: Some(function_name.clone()),
}
}
}
1 change: 1 addition & 0 deletions src/compilation/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod felt252_serde;
mod felt252_vec_compression;
pub mod replacer;
53 changes: 53 additions & 0 deletions src/compilation/utils/replacer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use cairo_lang_sierra::debug_info::DebugInfo;
use cairo_lang_sierra_generator::replace_ids::SierraIdReplacer;

pub struct SierraProgramDebugReplacer {
pub debug_info: DebugInfo,
}

impl SierraIdReplacer for SierraProgramDebugReplacer {
fn replace_libfunc_id(
&self,
id: &cairo_lang_sierra::ids::ConcreteLibfuncId,
) -> cairo_lang_sierra::ids::ConcreteLibfuncId {
let func_name = self
.debug_info
.libfunc_names
.get(id)
.expect("No libfunc in debug info");
cairo_lang_sierra::ids::ConcreteLibfuncId {
id: id.id,
debug_name: Some(func_name.clone()),
}
}

fn replace_type_id(
&self,
id: &cairo_lang_sierra::ids::ConcreteTypeId,
) -> cairo_lang_sierra::ids::ConcreteTypeId {
let type_name = self
.debug_info
.type_names
.get(id)
.expect("No typeid in debug info");
cairo_lang_sierra::ids::ConcreteTypeId {
id: id.id,
debug_name: Some(type_name.clone()),
}
}

fn replace_function_id(
&self,
sierra_id: &cairo_lang_sierra::ids::FunctionId,
) -> cairo_lang_sierra::ids::FunctionId {
let function_name = self
.debug_info
.user_func_names
.get(sierra_id)
.expect("No funcid in debug info");
cairo_lang_sierra::ids::FunctionId {
id: sierra_id.id,
debug_name: Some(function_name.clone()),
}
}
}
1 change: 1 addition & 0 deletions src/core/core_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::core::compilation_unit::CompilationUnit;
pub struct CoreOpts {
pub target: PathBuf,
pub corelib: Option<PathBuf>,
pub contract_path: Option<String>,
}

pub struct CoreUnit {
Expand Down
1 change: 1 addition & 0 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ fn test_detectors() {
corelib: Some(PathBuf::from(
env::var("CARGO_MANIFEST_DIR").unwrap() + "/corelib/src",
)),
contract_path: None,
};
let core = CoreUnit::new(opts).unwrap();
let mut results = get_detectors()
Expand Down