Skip to content

Commit

Permalink
Support cairo project (#47)
Browse files Browse the repository at this point in the history
* Support cairo project

* Update README.md
  • Loading branch information
smonicas committed Sep 20, 2023
1 parent 8249dcc commit d4e865e
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 54 deletions.
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

0 comments on commit d4e865e

Please sign in to comment.