1use std::path::{Path, PathBuf};
2use std::sync::Arc;
3
4use anyhow::{Context, Result};
5use cairo_lang_compiler::CompilerConfig;
6use cairo_lang_compiler::db::RootDatabase;
7use cairo_lang_compiler::project::setup_project;
8use cairo_lang_defs::ids::TopLevelLanguageElementId;
9use cairo_lang_diagnostics::ToOption;
10use cairo_lang_filesystem::ids::CrateId;
11use cairo_lang_lowering::db::LoweringGroup;
12use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId;
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};
23
24use crate::abi::AbiBuilder;
25use crate::aliased::Aliased;
26use crate::contract::{
27 ContractDeclaration, find_contracts, get_contract_abi_functions,
28 get_selector_and_sierra_function,
29};
30use crate::plugin::consts::{CONSTRUCTOR_MODULE, EXTERNAL_MODULE, L1_HANDLER_MODULE};
31use crate::starknet_plugin_suite;
32
33#[cfg(test)]
34#[path = "compile_test.rs"]
35mod test;
36
37pub fn compile_path(
40 path: &Path,
41 contract_path: Option<&str>,
42 mut compiler_config: CompilerConfig<'_>,
43) -> Result<ContractClass> {
44 let mut db = RootDatabase::builder()
45 .detect_corelib()
46 .with_default_plugin_suite(starknet_plugin_suite())
47 .build()?;
48
49 let main_crate_ids = setup_project(&mut db, Path::new(&path))?;
50 compiler_config.diagnostics_reporter =
51 compiler_config.diagnostics_reporter.with_crates(&main_crate_ids);
52 compile_contract_in_prepared_db(&db, contract_path, main_crate_ids, compiler_config)
53}
54
55pub fn compile_contract_in_prepared_db(
59 db: &RootDatabase,
60 contract_path: Option<&str>,
61 main_crate_ids: Vec<CrateId>,
62 mut compiler_config: CompilerConfig<'_>,
63) -> Result<ContractClass> {
64 let mut contracts = find_contracts(db, &main_crate_ids);
65
66 if let Some(contract_path) = contract_path {
68 contracts.retain(|contract| contract.submodule_id.full_path(db) == contract_path);
69 };
70 let contract = match contracts.len() {
71 0 => {
72 compiler_config.diagnostics_reporter.ensure(db)?;
74 anyhow::bail!("Contract not found.");
75 }
76 1 => &contracts[0],
77 _ => {
78 let contract_names =
79 contracts.iter().map(|contract| contract.submodule_id.full_path(db)).join("\n ");
80 anyhow::bail!(
81 "More than one contract found in the main crate: \n {}\nUse --contract-path to \
82 specify which to compile.",
83 contract_names
84 );
85 }
86 };
87
88 let contracts = vec![contract];
89 let mut classes = compile_prepared_db(db, &contracts, compiler_config)?;
90 assert_eq!(classes.len(), 1);
91 Ok(classes.remove(0))
92}
93
94pub fn compile_prepared_db(
105 db: &RootDatabase,
106 contracts: &[&ContractDeclaration],
107 mut compiler_config: CompilerConfig<'_>,
108) -> Result<Vec<ContractClass>> {
109 compiler_config.diagnostics_reporter.ensure(db)?;
110
111 contracts
112 .iter()
113 .map(|contract| {
114 compile_contract_with_prepared_and_checked_db(db, contract, &compiler_config)
115 })
116 .try_collect()
117}
118
119fn compile_contract_with_prepared_and_checked_db(
125 db: &RootDatabase,
126 contract: &ContractDeclaration,
127 compiler_config: &CompilerConfig<'_>,
128) -> Result<ContractClass> {
129 let SemanticEntryPoints { external, l1_handler, constructor } =
130 extract_semantic_entrypoints(db, contract)?;
131 let SierraProgramWithDebug { program: mut sierra_program, debug_info } = Arc::unwrap_or_clone(
132 db.get_sierra_program_for_functions(
133 chain!(&external, &l1_handler, &constructor).map(|f| f.value).collect(),
134 )
135 .to_option()
136 .with_context(|| "Compilation failed without any diagnostics.")?,
137 );
138
139 if compiler_config.replace_ids {
140 sierra_program = replace_sierra_ids_in_program(db, &sierra_program);
141 }
142 let replacer = CanonicalReplacer::from_program(&sierra_program);
143 let sierra_program = replacer.apply(&sierra_program);
144
145 let entry_points_by_type = ContractEntryPoints {
146 external: get_entry_points(db, &external, &replacer)?,
147 l1_handler: get_entry_points(db, &l1_handler, &replacer)?,
148 constructor: get_entry_points(db, &constructor, &replacer)?,
150 };
151
152 let mut annotations = Annotations::default();
153
154 if compiler_config.add_statements_functions {
155 let statements_functions = debug_info.statements_locations.extract_statements_functions(db);
156 annotations.extend(Annotations::from(statements_functions))
157 };
158
159 if compiler_config.add_statements_code_locations {
160 let statements_functions =
161 debug_info.statements_locations.extract_statements_source_code_locations(db);
162 annotations.extend(Annotations::from(statements_functions))
163 };
164
165 let contract_class = ContractClass::new(
166 &sierra_program,
167 entry_points_by_type,
168 Some(
169 AbiBuilder::from_submodule(db, contract.submodule_id, Default::default())
170 .ok()
171 .with_context(|| "Unexpected error while generating ABI.")?
172 .finalize()
173 .with_context(|| "Could not create ABI from contract submodule")?,
174 ),
175 annotations,
176 )?;
177 contract_class.sanity_check();
178 Ok(contract_class)
179}
180
181pub struct SemanticEntryPoints {
182 pub external: Vec<Aliased<ConcreteFunctionWithBodyId>>,
183 pub l1_handler: Vec<Aliased<ConcreteFunctionWithBodyId>>,
184 pub constructor: Vec<Aliased<ConcreteFunctionWithBodyId>>,
185}
186
187pub fn extract_semantic_entrypoints(
189 db: &dyn LoweringGroup,
190 contract: &ContractDeclaration,
191) -> core::result::Result<SemanticEntryPoints, anyhow::Error> {
192 let external: Vec<_> = get_contract_abi_functions(db.upcast(), contract, EXTERNAL_MODULE)?
193 .into_iter()
194 .map(|f| f.map(|f| ConcreteFunctionWithBodyId::from_semantic(db, f)))
195 .collect();
196 let l1_handler: Vec<_> = get_contract_abi_functions(db.upcast(), contract, L1_HANDLER_MODULE)?
197 .into_iter()
198 .map(|f| f.map(|f| ConcreteFunctionWithBodyId::from_semantic(db, f)))
199 .collect();
200 let constructor: Vec<_> =
201 get_contract_abi_functions(db.upcast(), 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(
213 db: &RootDatabase,
214 entry_point_functions: &[Aliased<ConcreteFunctionWithBodyId>],
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 allowed_libfuncs_list: Option<ListSelector>,
237) -> anyhow::Result<String> {
238 let contract = compile_path(&crate_path, contract_path.as_deref(), config.unwrap_or_default())?;
239 contract.validate_version_compatible(allowed_libfuncs_list.unwrap_or_default())?;
240 serde_json::to_string_pretty(&contract).with_context(|| "Serialization failed.")
241}