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 compiler_config: CompilerConfig<'_>,
43) -> Result<ContractClass> {
44 let mut db = RootDatabase::builder()
45 .detect_corelib()
46 .with_plugin_suite(starknet_plugin_suite())
47 .build()?;
48
49 let main_crate_ids = setup_project(&mut db, Path::new(&path))?;
50
51 compile_contract_in_prepared_db(&db, contract_path, main_crate_ids, compiler_config)
52}
53
54pub fn compile_contract_in_prepared_db(
58 db: &RootDatabase,
59 contract_path: Option<&str>,
60 main_crate_ids: Vec<CrateId>,
61 mut compiler_config: CompilerConfig<'_>,
62) -> Result<ContractClass> {
63 let mut contracts = find_contracts(db, &main_crate_ids);
64
65 if let Some(contract_path) = contract_path {
67 contracts.retain(|contract| contract.submodule_id.full_path(db) == contract_path);
68 };
69 let contract = match contracts.len() {
70 0 => {
71 compiler_config.diagnostics_reporter.ensure(db)?;
73 anyhow::bail!("Contract not found.");
74 }
75 1 => &contracts[0],
76 _ => {
77 let contract_names =
78 contracts.iter().map(|contract| contract.submodule_id.full_path(db)).join("\n ");
79 anyhow::bail!(
80 "More than one contract found in the main crate: \n {}\nUse --contract-path to \
81 specify which to compile.",
82 contract_names
83 );
84 }
85 };
86
87 let contracts = vec![contract];
88 let mut classes = compile_prepared_db(db, &contracts, compiler_config)?;
89 assert_eq!(classes.len(), 1);
90 Ok(classes.remove(0))
91}
92
93pub fn compile_prepared_db(
104 db: &RootDatabase,
105 contracts: &[&ContractDeclaration],
106 mut compiler_config: CompilerConfig<'_>,
107) -> Result<Vec<ContractClass>> {
108 compiler_config.diagnostics_reporter.ensure(db)?;
109
110 contracts
111 .iter()
112 .map(|contract| {
113 compile_contract_with_prepared_and_checked_db(db, contract, &compiler_config)
114 })
115 .try_collect()
116}
117
118fn compile_contract_with_prepared_and_checked_db(
124 db: &RootDatabase,
125 contract: &ContractDeclaration,
126 compiler_config: &CompilerConfig<'_>,
127) -> Result<ContractClass> {
128 let SemanticEntryPoints { external, l1_handler, constructor } =
129 extract_semantic_entrypoints(db, contract)?;
130 let SierraProgramWithDebug { program: mut sierra_program, debug_info } = Arc::unwrap_or_clone(
131 db.get_sierra_program_for_functions(
132 chain!(&external, &l1_handler, &constructor).map(|f| f.value).collect(),
133 )
134 .to_option()
135 .with_context(|| "Compilation failed without any diagnostics.")?,
136 );
137
138 if compiler_config.replace_ids {
139 sierra_program = replace_sierra_ids_in_program(db, &sierra_program);
140 }
141 let replacer = CanonicalReplacer::from_program(&sierra_program);
142 let sierra_program = replacer.apply(&sierra_program);
143
144 let entry_points_by_type = ContractEntryPoints {
145 external: get_entry_points(db, &external, &replacer)?,
146 l1_handler: get_entry_points(db, &l1_handler, &replacer)?,
147 constructor: get_entry_points(db, &constructor, &replacer)?,
149 };
150
151 let mut annotations = Annotations::default();
152
153 if compiler_config.add_statements_functions {
154 let statements_functions = debug_info.statements_locations.extract_statements_functions(db);
155 annotations.extend(Annotations::from(statements_functions))
156 };
157
158 if compiler_config.add_statements_code_locations {
159 let statements_functions =
160 debug_info.statements_locations.extract_statements_source_code_locations(db);
161 annotations.extend(Annotations::from(statements_functions))
162 };
163
164 let contract_class = ContractClass::new(
165 &sierra_program,
166 entry_points_by_type,
167 Some(
168 AbiBuilder::from_submodule(db, contract.submodule_id, Default::default())
169 .ok()
170 .with_context(|| "Unexpected error while generating ABI.")?
171 .finalize()
172 .with_context(|| "Could not create ABI from contract submodule")?,
173 ),
174 annotations,
175 )?;
176 contract_class.sanity_check();
177 Ok(contract_class)
178}
179
180pub struct SemanticEntryPoints {
181 pub external: Vec<Aliased<ConcreteFunctionWithBodyId>>,
182 pub l1_handler: Vec<Aliased<ConcreteFunctionWithBodyId>>,
183 pub constructor: Vec<Aliased<ConcreteFunctionWithBodyId>>,
184}
185
186pub fn extract_semantic_entrypoints(
188 db: &dyn LoweringGroup,
189 contract: &ContractDeclaration,
190) -> core::result::Result<SemanticEntryPoints, anyhow::Error> {
191 let external: Vec<_> = get_contract_abi_functions(db.upcast(), contract, EXTERNAL_MODULE)?
192 .into_iter()
193 .map(|f| f.map(|f| ConcreteFunctionWithBodyId::from_semantic(db, f)))
194 .collect();
195 let l1_handler: Vec<_> = get_contract_abi_functions(db.upcast(), contract, L1_HANDLER_MODULE)?
196 .into_iter()
197 .map(|f| f.map(|f| ConcreteFunctionWithBodyId::from_semantic(db, f)))
198 .collect();
199 let constructor: Vec<_> =
200 get_contract_abi_functions(db.upcast(), contract, CONSTRUCTOR_MODULE)?
201 .into_iter()
202 .map(|f| f.map(|f| ConcreteFunctionWithBodyId::from_semantic(db, f)))
203 .collect();
204 if constructor.len() > 1 {
205 anyhow::bail!("Expected at most one constructor.");
206 }
207 Ok(SemanticEntryPoints { external, l1_handler, constructor })
208}
209
210fn get_entry_points(
212 db: &RootDatabase,
213 entry_point_functions: &[Aliased<ConcreteFunctionWithBodyId>],
214 replacer: &CanonicalReplacer,
215) -> Result<Vec<ContractEntryPoint>> {
216 let mut entry_points = vec![];
217 for function_with_body_id in entry_point_functions {
218 let (selector, sierra_id) =
219 get_selector_and_sierra_function(db, function_with_body_id, replacer);
220
221 entry_points.push(ContractEntryPoint {
222 selector: selector.to_biguint(),
223 function_idx: sierra_id.id as usize,
224 });
225 }
226 entry_points.sort_by(|a, b| a.selector.cmp(&b.selector));
227 Ok(entry_points)
228}
229
230pub fn starknet_compile(
232 crate_path: PathBuf,
233 contract_path: Option<String>,
234 config: Option<CompilerConfig<'_>>,
235 allowed_libfuncs_list: Option<ListSelector>,
236) -> anyhow::Result<String> {
237 let contract = compile_path(&crate_path, contract_path.as_deref(), config.unwrap_or_default())?;
238 contract.validate_version_compatible(allowed_libfuncs_list.unwrap_or_default())?;
239 serde_json::to_string_pretty(&contract).with_context(|| "Serialization failed.")
240}