fe_driver/
lib.rs

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
18/// The artifacts of a compiled module.
19pub struct CompiledModule {
20    pub src_ast: String,
21    pub lowered_ast: String,
22    pub contracts: IndexMap<String, CompiledContract>,
23}
24
25/// The artifacts of a compiled contract.
26pub 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
147// Run analysis with ingot
148// Return vector error,waring...
149pub 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
157/// Compiles the main module of a project.
158///
159/// If `with_bytecode` is set to false, the compiler will skip the final Yul ->
160/// Bytecode pass. This is useful when debugging invalid Yul code.
161pub 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
214/// Returns graphviz string.
215// TODO: This is temporary function for debugging.
216pub 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            // Maybe put the ContractID here so we can trace it back to the source file
279            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}