1use std::path::{Path, PathBuf};
2
3use anyhow::{Context, Result};
4use cairo_lang_compiler::db::RootDatabase;
5use cairo_lang_compiler::project::setup_project;
6use cairo_lang_compiler::{CompilerConfig, ensure_diagnostics};
7use cairo_lang_defs::ids::TopLevelLanguageElementId;
8use cairo_lang_diagnostics::ToOption;
9use cairo_lang_filesystem::ids::{CrateId, CrateInput};
10use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId;
11use cairo_lang_lowering::optimizations::config::Optimizations;
12use cairo_lang_lowering::utils::InliningStrategy;
13use cairo_lang_sierra::debug_info::Annotations;
14use cairo_lang_sierra_generator::canonical_id_replacer::CanonicalReplacer;
15use cairo_lang_sierra_generator::db::SierraGenGroup;
16use cairo_lang_sierra_generator::program_generator::SierraProgramWithDebug;
17use cairo_lang_sierra_generator::replace_ids::{SierraIdReplacer, replace_sierra_ids_in_program};
18use cairo_lang_starknet_classes::allowed_libfuncs::ListSelector;
19use cairo_lang_starknet_classes::contract_class::{
20 ContractClass, ContractEntryPoint, ContractEntryPoints,
21};
22use itertools::{Itertools, chain};
23use salsa::{Database, par_map};
24
25use crate::abi::AbiBuilder;
26use crate::aliased::Aliased;
27use crate::contract::{
28 ContractDeclaration, find_contracts, get_contract_abi_functions,
29 get_selector_and_sierra_function,
30};
31use crate::plugin::consts::{CONSTRUCTOR_MODULE, EXTERNAL_MODULE, L1_HANDLER_MODULE};
32use crate::starknet_plugin_suite;
33
34#[cfg(test)]
35#[path = "compile_test.rs"]
36mod test;
37
38pub fn compile_path(
41 path: &Path,
42 contract_path: Option<&str>,
43 mut compiler_config: CompilerConfig<'_>,
44 inlining_strategy: InliningStrategy,
45) -> Result<ContractClass> {
46 let mut db = RootDatabase::builder()
47 .with_optimizations(Optimizations::enabled_with_default_movable_functions(
48 inlining_strategy,
49 ))
50 .detect_corelib()
51 .with_default_plugin_suite(starknet_plugin_suite())
52 .build()?;
53
54 let main_crate_inputs = setup_project(&mut db, Path::new(&path))?;
55 compiler_config.diagnostics_reporter =
56 compiler_config.diagnostics_reporter.with_crates(&main_crate_inputs);
57 let main_crate_ids = CrateInput::into_crate_ids(&db, main_crate_inputs);
58 compile_contract_in_prepared_db(&db, contract_path, main_crate_ids, compiler_config)
59}
60
61pub fn compile_contract_in_prepared_db<'db>(
65 db: &'db dyn Database,
66 contract_path: Option<&str>,
67 main_crate_ids: Vec<CrateId<'db>>,
68 mut compiler_config: CompilerConfig<'_>,
69) -> Result<ContractClass> {
70 let mut contracts = find_contracts(db, &main_crate_ids);
71
72 if let Some(contract_path) = contract_path {
74 contracts.retain(|contract| contract.submodule_id.full_path(db) == contract_path);
75 };
76 let contract = match contracts.len() {
77 0 => {
78 compiler_config.diagnostics_reporter.ensure(db)?;
80 anyhow::bail!("Contract not found.");
81 }
82 1 => &contracts[0],
83 _ => {
84 let contract_names =
85 contracts.iter().map(|contract| contract.submodule_id.full_path(db)).join("\n ");
86 anyhow::bail!(
87 "More than one contract found in the main crate: \n {}\nUse --contract-path to \
88 specify which to compile.",
89 contract_names
90 );
91 }
92 };
93
94 let contracts = vec![contract];
95 let mut classes = compile_prepared_db(db, &contracts, compiler_config)?;
96 assert_eq!(classes.len(), 1);
97 Ok(classes.remove(0))
98}
99
100pub fn compile_prepared_db<'db>(
111 db: &'db dyn Database,
112 contracts: &[&ContractDeclaration<'db>],
113 mut compiler_config: CompilerConfig<'_>,
114) -> Result<Vec<ContractClass>> {
115 ensure_diagnostics(db, &mut compiler_config.diagnostics_reporter)?;
116
117 par_map(db, contracts, |db, contract| {
118 compile_contract_with_prepared_and_checked_db(db, contract, &compiler_config)
119 })
120}
121
122fn compile_contract_with_prepared_and_checked_db<'db>(
128 db: &'db dyn Database,
129 contract: &ContractDeclaration<'db>,
130 compiler_config: &CompilerConfig<'_>,
131) -> Result<ContractClass> {
132 let SemanticEntryPoints { external, l1_handler, constructor } =
133 extract_semantic_entrypoints(db, contract)?;
134 let SierraProgramWithDebug { program: mut sierra_program, debug_info } = db
135 .get_sierra_program_for_functions(
136 chain!(&external, &l1_handler, &constructor).map(|f| f.value).collect(),
137 )
138 .to_option()
139 .context("Compilation failed without any diagnostics.")?
140 .clone();
141
142 if compiler_config.replace_ids {
143 sierra_program = replace_sierra_ids_in_program(db, &sierra_program);
144 }
145 let replacer = CanonicalReplacer::from_program(&sierra_program);
146 let sierra_program = replacer.apply(&sierra_program);
147
148 let entry_points_by_type = ContractEntryPoints {
149 external: get_entry_points(db, &external, &replacer)?,
150 l1_handler: get_entry_points(db, &l1_handler, &replacer)?,
151 constructor: get_entry_points(db, &constructor, &replacer)?,
153 };
154
155 let mut annotations = Annotations::default();
156
157 if compiler_config.add_statements_functions {
158 let statements_functions = debug_info.statements_locations.extract_statements_functions(db);
159 annotations.extend(Annotations::from(statements_functions))
160 };
161
162 if compiler_config.add_statements_code_locations {
163 let statements_functions =
164 debug_info.statements_locations.extract_statements_source_code_locations(db);
165 annotations.extend(Annotations::from(statements_functions))
166 };
167
168 let abi_builder: Option<AbiBuilder<'db>> =
169 AbiBuilder::from_submodule(db, contract.submodule_id, Default::default()).ok();
170 let finalized_abi =
171 abi_builder.with_context(|| "Unexpected error while generating ABI.")?.finalize();
172 let abi = match finalized_abi {
173 Ok(abi) => abi,
174 Err(e) => anyhow::bail!("Could not create ABI from contract submodule: {}", e),
175 };
176 let contract_class =
177 ContractClass::new(&sierra_program, entry_points_by_type, Some(abi), annotations)?;
178 contract_class.sanity_check();
179 Ok(contract_class)
180}
181
182pub struct SemanticEntryPoints<'db> {
183 pub external: Vec<Aliased<ConcreteFunctionWithBodyId<'db>>>,
184 pub l1_handler: Vec<Aliased<ConcreteFunctionWithBodyId<'db>>>,
185 pub constructor: Vec<Aliased<ConcreteFunctionWithBodyId<'db>>>,
186}
187
188pub fn extract_semantic_entrypoints<'db>(
190 db: &'db dyn Database,
191 contract: &ContractDeclaration<'db>,
192) -> core::result::Result<SemanticEntryPoints<'db>, anyhow::Error> {
193 let external: Vec<_> = get_contract_abi_functions(db, contract, EXTERNAL_MODULE)?
194 .into_iter()
195 .map(|f| f.map(|f| ConcreteFunctionWithBodyId::from_semantic(db, f)))
196 .collect();
197 let l1_handler: Vec<_> = get_contract_abi_functions(db, contract, L1_HANDLER_MODULE)?
198 .into_iter()
199 .map(|f| f.map(|f| ConcreteFunctionWithBodyId::from_semantic(db, f)))
200 .collect();
201 let constructor: Vec<_> = get_contract_abi_functions(db, contract, CONSTRUCTOR_MODULE)?
202 .into_iter()
203 .map(|f| f.map(|f| ConcreteFunctionWithBodyId::from_semantic(db, f)))
204 .collect();
205 if constructor.len() > 1 {
206 anyhow::bail!("Expected at most one constructor.");
207 }
208 Ok(SemanticEntryPoints { external, l1_handler, constructor })
209}
210
211fn get_entry_points<'db>(
213 db: &'db dyn Database,
214 entry_point_functions: &[Aliased<ConcreteFunctionWithBodyId<'db>>],
215 replacer: &CanonicalReplacer,
216) -> Result<Vec<ContractEntryPoint>> {
217 let mut entry_points = vec![];
218 for function_with_body_id in entry_point_functions {
219 let (selector, sierra_id) =
220 get_selector_and_sierra_function(db, function_with_body_id, replacer);
221
222 entry_points.push(ContractEntryPoint {
223 selector: selector.to_biguint(),
224 function_idx: sierra_id.id as usize,
225 });
226 }
227 entry_points.sort_by(|a, b| a.selector.cmp(&b.selector));
228 Ok(entry_points)
229}
230
231pub fn starknet_compile(
233 crate_path: PathBuf,
234 contract_path: Option<String>,
235 config: Option<CompilerConfig<'_>>,
236 inlining_strategy: InliningStrategy,
237 allowed_libfuncs_list: Option<ListSelector>,
238) -> Result<String> {
239 let config = config.unwrap_or_default();
240 let contract = compile_path(&crate_path, contract_path.as_deref(), config, inlining_strategy)?;
241 contract.validate_version_compatible(allowed_libfuncs_list.unwrap_or_default())?;
242 serde_json::to_string_pretty(&contract).with_context(|| "Serialization failed.")
243}