1#![allow(unused_imports, dead_code)]
2
3use fe_abi::event::AbiEvent;
4use fe_abi::types::{AbiTupleField, AbiType};
5pub use fe_codegen::db::{CodegenDb, Db};
6
7use fe_analyzer::namespace::items::{ContractId, FunctionId, IngotId, IngotMode, ModuleId};
8use fe_common::diagnostics::Diagnostic;
9use fe_common::files::FileKind;
10use fe_common::{db::Upcast, utils::files::BuildFiles};
11use fe_parser::ast::SmolStr;
12use fe_test_runner::ethabi::{Event, EventParam, ParamType};
13use fe_test_runner::TestSink;
14use indexmap::{indexmap, IndexMap};
15use serde_json::Value;
16use std::fmt::Display;
17
18pub struct CompiledModule {
20 pub src_ast: String,
21 pub lowered_ast: String,
22 pub contracts: IndexMap<String, CompiledContract>,
23}
24
25pub struct CompiledContract {
27 pub json_abi: String,
28 pub yul: String,
29 pub origin: ContractId,
30 #[cfg(feature = "solc-backend")]
31 pub bytecode: String,
32 #[cfg(feature = "solc-backend")]
33 pub runtime_bytecode: String,
34}
35
36#[cfg(feature = "solc-backend")]
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct CompiledTest {
39 pub name: SmolStr,
40 events: Vec<AbiEvent>,
41 bytecode: String,
42}
43
44#[cfg(feature = "solc-backend")]
45impl CompiledTest {
46 pub fn new(name: SmolStr, events: Vec<AbiEvent>, bytecode: String) -> Self {
47 Self {
48 name,
49 events,
50 bytecode,
51 }
52 }
53
54 pub fn execute(&self, sink: &mut TestSink) -> bool {
55 let events = map_abi_events(&self.events);
56 fe_test_runner::execute(&self.name, &events, &self.bytecode, sink)
57 }
58}
59
60fn map_abi_events(events: &[AbiEvent]) -> Vec<Event> {
61 events.iter().map(map_abi_event).collect()
62}
63
64fn map_abi_event(event: &AbiEvent) -> Event {
65 let inputs = event
66 .inputs
67 .iter()
68 .map(|input| {
69 let kind = map_abi_type(&input.ty);
70 EventParam {
71 name: input.name.to_owned(),
72 kind,
73 indexed: input.indexed,
74 }
75 })
76 .collect();
77 Event {
78 name: event.name.to_owned(),
79 inputs,
80 anonymous: event.anonymous,
81 }
82}
83
84fn map_abi_type(typ: &AbiType) -> ParamType {
85 match typ {
86 AbiType::UInt(value) => ParamType::Uint(*value),
87 AbiType::Int(value) => ParamType::Int(*value),
88 AbiType::Address => ParamType::Address,
89 AbiType::Bool => ParamType::Bool,
90 AbiType::Function => panic!("function cannot be mapped to an actual ABI value type"),
91 AbiType::Array { elem_ty, len } => {
92 ParamType::FixedArray(Box::new(map_abi_type(elem_ty)), *len)
93 }
94 AbiType::Tuple(params) => ParamType::Tuple(map_abi_types(params)),
95 AbiType::Bytes => ParamType::Bytes,
96 AbiType::String => ParamType::String,
97 }
98}
99
100fn map_abi_types(fields: &[AbiTupleField]) -> Vec<ParamType> {
101 fields.iter().map(|field| map_abi_type(&field.ty)).collect()
102}
103
104#[derive(Debug)]
105pub struct CompileError(pub Vec<Diagnostic>);
106
107pub fn check_single_file(db: &mut Db, path: &str, src: &str) -> Vec<Diagnostic> {
108 let module = ModuleId::new_standalone(db, path, src);
109 module.diagnostics(db)
110}
111
112pub fn compile_single_file(
113 db: &mut Db,
114 path: &str,
115 src: &str,
116 with_bytecode: bool,
117 with_runtime_bytecode: bool,
118 optimize: bool,
119) -> Result<CompiledModule, CompileError> {
120 let module = ModuleId::new_standalone(db, path, src);
121 let diags = module.diagnostics(db);
122
123 if diags.is_empty() {
124 compile_module(db, module, with_bytecode, with_runtime_bytecode, optimize)
125 } else {
126 Err(CompileError(diags))
127 }
128}
129
130#[cfg(feature = "solc-backend")]
131pub fn compile_single_file_tests(
132 db: &mut Db,
133 path: &str,
134 src: &str,
135 optimize: bool,
136) -> Result<(SmolStr, Vec<CompiledTest>), CompileError> {
137 let module = ModuleId::new_standalone(db, path, src);
138 let diags = module.diagnostics(db);
139
140 if diags.is_empty() {
141 Ok((module.name(db), compile_module_tests(db, module, optimize)))
142 } else {
143 Err(CompileError(diags))
144 }
145}
146
147pub fn check_ingot(db: &mut Db, build_files: &BuildFiles) -> Vec<Diagnostic> {
150 let ingot = IngotId::from_build_files(db, build_files);
151
152 let mut diags = ingot.diagnostics(db);
153 ingot.sink_external_ingot_diagnostics(db, &mut diags);
154 diags
155}
156
157pub fn compile_ingot(
162 db: &mut Db,
163 build_files: &BuildFiles,
164 with_bytecode: bool,
165 with_runtime_bytecode: bool,
166 optimize: bool,
167) -> Result<CompiledModule, CompileError> {
168 let ingot = IngotId::from_build_files(db, build_files);
169
170 let mut diags = ingot.diagnostics(db);
171 ingot.sink_external_ingot_diagnostics(db, &mut diags);
172 if !diags.is_empty() {
173 return Err(CompileError(diags));
174 }
175 let main_module = ingot
176 .root_module(db)
177 .expect("missing root module, with no diagnostic");
178 compile_module(
179 db,
180 main_module,
181 with_bytecode,
182 with_runtime_bytecode,
183 optimize,
184 )
185}
186
187#[cfg(feature = "solc-backend")]
188pub fn compile_ingot_tests(
189 db: &mut Db,
190 build_files: &BuildFiles,
191 optimize: bool,
192) -> Result<Vec<(SmolStr, Vec<CompiledTest>)>, CompileError> {
193 let ingot = IngotId::from_build_files(db, build_files);
194
195 let mut diags = ingot.diagnostics(db);
196 ingot.sink_external_ingot_diagnostics(db, &mut diags);
197 if !diags.is_empty() {
198 return Err(CompileError(diags));
199 }
200
201 if diags.is_empty() {
202 Ok(ingot
203 .all_modules(db)
204 .iter()
205 .fold(vec![], |mut accum, module| {
206 accum.push((module.name(db), compile_module_tests(db, *module, optimize)));
207 accum
208 }))
209 } else {
210 Err(CompileError(diags))
211 }
212}
213
214pub fn dump_mir_single_file(db: &mut Db, path: &str, src: &str) -> Result<String, CompileError> {
217 let module = ModuleId::new_standalone(db, path, src);
218
219 let diags = module.diagnostics(db);
220 if !diags.is_empty() {
221 return Err(CompileError(diags));
222 }
223
224 let mut text = vec![];
225 fe_mir::graphviz::write_mir_graphs(db, module, &mut text).unwrap();
226 Ok(String::from_utf8(text).unwrap())
227}
228
229#[cfg(feature = "solc-backend")]
230fn compile_test(db: &mut Db, test: FunctionId, optimize: bool) -> CompiledTest {
231 let yul_test = fe_codegen::yul::isel::lower_test(db, test)
232 .to_string()
233 .replace('"', "\\\"");
234 let bytecode = compile_to_evm("test", &yul_test, optimize, false).bytecode;
235 let events = db.codegen_abi_module_events(test.module(db));
236 CompiledTest::new(test.name(db), events, bytecode)
237}
238
239#[cfg(feature = "solc-backend")]
240fn compile_module_tests(db: &mut Db, module_id: ModuleId, optimize: bool) -> Vec<CompiledTest> {
241 module_id
242 .tests(db)
243 .iter()
244 .map(|test| compile_test(db, *test, optimize))
245 .collect()
246}
247
248#[cfg(feature = "solc-backend")]
249fn compile_module(
250 db: &mut Db,
251 module_id: ModuleId,
252 with_bytecode: bool,
253 with_runtime_bytecode: bool,
254 optimize: bool,
255) -> Result<CompiledModule, CompileError> {
256 let mut contracts = IndexMap::default();
257
258 for contract in module_id.all_contracts(db.upcast()) {
259 let name = &contract.data(db.upcast()).name;
260 let abi = db.codegen_abi_contract(contract);
261 let yul_contract = compile_to_yul(db, contract);
262
263 let (bytecode, runtime_bytecode) = if with_bytecode || with_runtime_bytecode {
264 let deployable_name = db.codegen_contract_deployer_symbol_name(contract);
265 let bytecode = compile_to_evm(
266 deployable_name.as_str(),
267 &yul_contract,
268 optimize,
269 with_runtime_bytecode,
270 );
271 (bytecode.bytecode, bytecode.runtime_bytecode)
272 } else {
273 ("".to_string(), "".to_string())
274 };
275
276 contracts.insert(
277 name.to_string(),
278 CompiledContract {
280 json_abi: serde_json::to_string_pretty(&abi).unwrap(),
281 yul: yul_contract,
282 origin: contract,
283 bytecode,
284 runtime_bytecode,
285 },
286 );
287 }
288
289 Ok(CompiledModule {
290 src_ast: format!("{:#?}", module_id.ast(db)),
291 lowered_ast: format!("{:#?}", module_id.ast(db)),
292 contracts,
293 })
294}
295
296#[cfg(not(feature = "solc-backend"))]
297fn compile_module(
298 db: &mut Db,
299 module_id: ModuleId,
300 _with_bytecode: bool,
301 _with_runtime_bytecode: bool,
302 _optimize: bool,
303) -> Result<CompiledModule, CompileError> {
304 let mut contracts = IndexMap::default();
305 for contract in module_id.all_contracts(db.upcast()) {
306 let name = &contract.data(db.upcast()).name;
307 let abi = db.codegen_abi_contract(contract);
308 let yul_contract = compile_to_yul(db, contract);
309
310 contracts.insert(
311 name.to_string(),
312 CompiledContract {
313 json_abi: serde_json::to_string_pretty(&abi).unwrap(),
314 yul: yul_contract,
315 origin: contract,
316 },
317 );
318 }
319
320 Ok(CompiledModule {
321 src_ast: format!("{:#?}", module_id.ast(db)),
322 lowered_ast: format!("{:#?}", module_id.ast(db)),
323 contracts,
324 })
325}
326
327fn compile_to_yul(db: &mut Db, contract: ContractId) -> String {
328 let yul_contract = fe_codegen::yul::isel::lower_contract_deployable(db, contract);
329 yul_contract.to_string().replace('"', "\\\"")
330}
331
332#[cfg(feature = "solc-backend")]
333fn compile_to_evm(
334 name: &str,
335 yul_object: &str,
336 optimize: bool,
337 verify_runtime_bytecode: bool,
338) -> fe_yulc::ContractBytecode {
339 match fe_yulc::compile_single_contract(name, yul_object, optimize, verify_runtime_bytecode) {
340 Ok(bytecode) => bytecode,
341
342 Err(error) => {
343 for error in serde_json::from_str::<Value>(&error.0)
344 .expect("unable to deserialize json output")["errors"]
345 .as_array()
346 .expect("errors not an array")
347 {
348 eprintln!(
349 "Error: {}",
350 error["formattedMessage"]
351 .as_str()
352 .expect("error value not a string")
353 .replace("\\\n", "\n")
354 )
355 }
356 panic!("Yul compilation failed with the above errors")
357 }
358 }
359}