1use super::{
2 restrictions::CompilerSettingsRestrictions, CompilationError, Compiler, CompilerInput,
3 CompilerOutput, CompilerSettings, CompilerVersion, Language, ParsedSource,
4};
5use crate::{
6 resolver::{
7 parse::{SolData, SolParser},
8 Node,
9 },
10 SourceParser,
11};
12use foundry_compilers_artifacts::{
13 error::SourceLocation,
14 output_selection::OutputSelection,
15 remappings::Remapping,
16 sources::{Source, Sources},
17 BytecodeHash, Contract, Error, EvmVersion, Settings, Severity, SolcInput,
18};
19use foundry_compilers_core::error::{Result, SolcError, SolcIoError};
20use rayon::prelude::*;
21use semver::Version;
22use serde::{Deserialize, Serialize};
23use std::{
24 borrow::Cow,
25 collections::{BTreeMap, BTreeSet},
26 ops::{Deref, DerefMut},
27 path::{Path, PathBuf},
28};
29
30pub use foundry_compilers_artifacts::SolcLanguage;
31
32mod compiler;
33pub use compiler::{Solc, SOLC_EXTENSIONS};
34
35#[derive(Clone, Debug)]
36#[cfg_attr(feature = "svm-solc", derive(Default))]
37pub enum SolcCompiler {
38 #[default]
39 #[cfg(feature = "svm-solc")]
40 AutoDetect,
41
42 Specific(Solc),
43}
44
45impl Language for SolcLanguage {
46 const FILE_EXTENSIONS: &'static [&'static str] = SOLC_EXTENSIONS;
47}
48
49impl Compiler for SolcCompiler {
50 type Input = SolcVersionedInput;
51 type CompilationError = Error;
52 type Parser = SolParser;
53 type Settings = SolcSettings;
54 type Language = SolcLanguage;
55 type CompilerContract = Contract;
56
57 fn compile(
58 &self,
59 input: &Self::Input,
60 ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>> {
61 let mut solc = match self {
62 Self::Specific(solc) => solc.clone(),
63
64 #[cfg(feature = "svm-solc")]
65 Self::AutoDetect => Solc::find_or_install(&input.version)?,
66 };
67 solc.base_path.clone_from(&input.cli_settings.base_path);
68 solc.allow_paths.clone_from(&input.cli_settings.allow_paths);
69 solc.include_paths.clone_from(&input.cli_settings.include_paths);
70 solc.extra_args.extend_from_slice(&input.cli_settings.extra_args);
71
72 let solc_output = solc.compile(&input.input)?;
73
74 let output = CompilerOutput {
75 errors: solc_output.errors,
76 contracts: solc_output.contracts,
77 sources: solc_output.sources,
78 metadata: BTreeMap::new(),
79 };
80
81 Ok(output)
82 }
83
84 fn available_versions(&self, _language: &Self::Language) -> Vec<CompilerVersion> {
85 match self {
86 Self::Specific(solc) => vec![CompilerVersion::Installed(Version::new(
87 solc.version.major,
88 solc.version.minor,
89 solc.version.patch,
90 ))],
91
92 #[cfg(feature = "svm-solc")]
93 Self::AutoDetect => {
94 let mut all_versions = Solc::installed_versions()
95 .into_iter()
96 .map(CompilerVersion::Installed)
97 .collect::<Vec<_>>();
98 let mut uniques = all_versions
99 .iter()
100 .map(|v| {
101 let v = v.as_ref();
102 (v.major, v.minor, v.patch)
103 })
104 .collect::<std::collections::HashSet<_>>();
105 all_versions.extend(
106 Solc::released_versions()
107 .into_iter()
108 .filter(|v| uniques.insert((v.major, v.minor, v.patch)))
109 .map(CompilerVersion::Remote),
110 );
111 all_versions.sort_unstable();
112 all_versions
113 }
114 }
115 }
116}
117
118#[derive(Clone, Debug, Serialize, Deserialize)]
119#[serde(rename_all = "camelCase")]
120pub struct SolcVersionedInput {
121 pub version: Version,
122 #[serde(flatten)]
123 pub input: SolcInput,
124 #[serde(flatten)]
125 pub cli_settings: CliSettings,
126}
127
128impl CompilerInput for SolcVersionedInput {
129 type Settings = SolcSettings;
130 type Language = SolcLanguage;
131
132 fn build(
137 sources: Sources,
138 settings: Self::Settings,
139 language: Self::Language,
140 version: Version,
141 ) -> Self {
142 let SolcSettings { settings, cli_settings } = settings;
143 let input = SolcInput::new(language, sources, settings).sanitized(&version);
144
145 Self { version, input, cli_settings }
146 }
147
148 fn language(&self) -> Self::Language {
149 self.input.language
150 }
151
152 fn version(&self) -> &Version {
153 &self.version
154 }
155
156 fn sources(&self) -> impl Iterator<Item = (&Path, &Source)> {
157 self.input.sources.iter().map(|(path, source)| (path.as_path(), source))
158 }
159
160 fn compiler_name(&self) -> Cow<'static, str> {
161 "Solc".into()
162 }
163
164 fn strip_prefix(&mut self, base: &Path) {
165 self.input.strip_prefix(base);
166 }
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
170#[serde(rename_all = "camelCase")]
171pub struct CliSettings {
172 #[serde(default, skip_serializing_if = "Vec::is_empty")]
173 pub extra_args: Vec<String>,
174 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
175 pub allow_paths: BTreeSet<PathBuf>,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
177 pub base_path: Option<PathBuf>,
178 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
179 pub include_paths: BTreeSet<PathBuf>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
183pub struct SolcSettings {
184 #[serde(flatten)]
186 pub settings: Settings,
187 #[serde(flatten)]
189 pub cli_settings: CliSettings,
190}
191
192impl Deref for SolcSettings {
193 type Target = Settings;
194
195 fn deref(&self) -> &Self::Target {
196 &self.settings
197 }
198}
199
200impl DerefMut for SolcSettings {
201 fn deref_mut(&mut self) -> &mut Self::Target {
202 &mut self.settings
203 }
204}
205
206#[derive(Debug, Clone, Copy, Eq, Default, PartialEq)]
208pub struct Restriction<V> {
209 pub min: Option<V>,
210 pub max: Option<V>,
211}
212
213impl<V: Ord + Copy> Restriction<V> {
214 pub fn satisfies(&self, value: Option<V>) -> bool {
218 self.min.is_none_or(|min| value.is_some_and(|v| v >= min))
219 && self.max.is_none_or(|max| value.is_some_and(|v| v <= max))
220 }
221
222 pub fn merge(self, other: Self) -> Option<Self> {
224 let Self { mut min, mut max } = self;
225 let Self { min: other_min, max: other_max } = other;
226
227 min = min.map_or(other_min, |this_min| {
228 Some(other_min.map_or(this_min, |other_min| this_min.max(other_min)))
229 });
230 max = max.map_or(other_max, |this_max| {
231 Some(other_max.map_or(this_max, |other_max| this_max.min(other_max)))
232 });
233
234 if let (Some(min), Some(max)) = (min, max) {
235 if min > max {
236 return None;
237 }
238 }
239
240 Some(Self { min, max })
241 }
242
243 pub fn apply(&self, value: Option<V>) -> Option<V> {
244 match (value, self.min, self.max) {
245 (None, Some(min), _) => Some(min),
246 (None, None, Some(max)) => Some(max),
247 (Some(cur), Some(min), _) if cur < min => Some(min),
248 (Some(cur), _, Some(max)) if cur > max => Some(max),
249 _ => value,
250 }
251 }
252}
253
254#[derive(Debug, Clone, Copy, Default)]
256pub struct SolcRestrictions {
257 pub evm_version: Restriction<EvmVersion>,
258 pub via_ir: Option<bool>,
259 pub optimizer_runs: Restriction<usize>,
260 pub bytecode_hash: Option<BytecodeHash>,
261}
262
263impl CompilerSettingsRestrictions for SolcRestrictions {
264 fn merge(self, other: Self) -> Option<Self> {
265 if let (Some(via_ir), Some(other_via_ir)) = (self.via_ir, other.via_ir) {
266 if via_ir != other_via_ir {
267 return None;
268 }
269 }
270
271 if let (Some(bytecode_hash), Some(other_bytecode_hash)) =
272 (self.bytecode_hash, other.bytecode_hash)
273 {
274 if bytecode_hash != other_bytecode_hash {
275 return None;
276 }
277 }
278
279 Some(Self {
280 evm_version: self.evm_version.merge(other.evm_version)?,
281 via_ir: self.via_ir.or(other.via_ir),
282 optimizer_runs: self.optimizer_runs.merge(other.optimizer_runs)?,
283 bytecode_hash: self.bytecode_hash.or(other.bytecode_hash),
284 })
285 }
286}
287
288impl CompilerSettings for SolcSettings {
289 type Restrictions = SolcRestrictions;
290
291 fn update_output_selection(&mut self, mut f: impl FnMut(&mut OutputSelection)) {
292 f(&mut self.settings.output_selection);
293 }
294
295 fn can_use_cached(&self, other: &Self) -> bool {
296 let Self {
297 settings:
298 Settings {
299 stop_after,
300 remappings,
301 optimizer,
302 model_checker,
303 metadata,
304 output_selection,
305 evm_version,
306 via_ir,
307 debug,
308 libraries,
309 },
310 ..
311 } = self;
312
313 *stop_after == other.settings.stop_after
314 && *remappings == other.settings.remappings
315 && *optimizer == other.settings.optimizer
316 && *model_checker == other.settings.model_checker
317 && *metadata == other.settings.metadata
318 && *evm_version == other.settings.evm_version
319 && *via_ir == other.settings.via_ir
320 && *debug == other.settings.debug
321 && *libraries == other.settings.libraries
322 && output_selection.is_subset_of(&other.settings.output_selection)
323 }
324
325 fn with_remappings(mut self, remappings: &[Remapping]) -> Self {
326 self.settings.remappings = remappings.to_vec();
327
328 self
329 }
330
331 fn with_allow_paths(mut self, allowed_paths: &BTreeSet<PathBuf>) -> Self {
332 self.cli_settings.allow_paths.clone_from(allowed_paths);
333 self
334 }
335
336 fn with_base_path(mut self, base_path: &Path) -> Self {
337 self.cli_settings.base_path = Some(base_path.to_path_buf());
338 self
339 }
340
341 fn with_include_paths(mut self, include_paths: &BTreeSet<PathBuf>) -> Self {
342 self.cli_settings.include_paths.clone_from(include_paths);
343 self
344 }
345
346 fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool {
347 let mut satisfies = true;
348
349 let SolcRestrictions { evm_version, via_ir, optimizer_runs, bytecode_hash } = restrictions;
350
351 satisfies &= evm_version.satisfies(self.evm_version);
352 satisfies &= via_ir.is_none_or(|via_ir| via_ir == self.via_ir.unwrap_or_default());
353 satisfies &= bytecode_hash.is_none_or(|bytecode_hash| {
354 self.metadata.as_ref().and_then(|m| m.bytecode_hash) == Some(bytecode_hash)
355 });
356 satisfies &= optimizer_runs.satisfies(self.optimizer.runs);
357
358 satisfies &= optimizer_runs
360 .min
361 .is_none_or(|min| min == 0 || self.optimizer.enabled.unwrap_or_default());
362
363 satisfies
364 }
365}
366
367impl SourceParser for SolParser {
368 type ParsedSource = SolData;
369
370 fn new(config: &crate::ProjectPathsConfig) -> Self {
371 Self {
372 compiler: solar::sema::Compiler::new(Self::session_with_opts(
373 solar::sema::interface::config::Opts {
374 include_paths: config.include_paths.iter().cloned().collect(),
375 base_path: Some(config.root.clone()),
376 import_remappings: config
377 .remappings
378 .iter()
379 .map(|r| solar::sema::interface::config::ImportRemapping {
380 context: r.context.clone().unwrap_or_default(),
381 prefix: r.name.clone(),
382 path: r.path.clone(),
383 })
384 .collect(),
385 ..Default::default()
386 },
387 )),
388 }
389 }
390
391 fn read(&mut self, path: &Path) -> Result<Node<Self::ParsedSource>> {
392 let mut sources = Sources::from_iter([(path.to_path_buf(), Source::read_(path)?)]);
393 let nodes = self.parse_sources(&mut sources)?;
394 debug_assert_eq!(nodes.len(), 1, "{nodes:#?}");
395 Ok(nodes.into_iter().next().unwrap().1)
396 }
397
398 fn parse_sources(
399 &mut self,
400 sources: &mut Sources,
401 ) -> Result<Vec<(PathBuf, Node<Self::ParsedSource>)>> {
402 self.compiler.enter_mut(|compiler| {
403 let mut pcx = compiler.parse();
404 pcx.set_resolve_imports(false);
405 let files = sources
406 .par_iter()
407 .map(|(path, source)| {
408 pcx.sess
409 .source_map()
410 .new_source_file(path.clone(), source.content.as_str())
411 .map_err(|e| SolcError::Io(SolcIoError::new(e, path)))
412 })
413 .collect::<Result<Vec<_>>>()?;
414 pcx.add_files(files);
415 pcx.parse();
416
417 let parsed = sources.par_iter().map(|(path, source)| {
418 let sf = compiler.sess().source_map().get_file(path).unwrap();
419 let (_, s) = compiler.gcx().sources.get_file(&sf).unwrap();
420 let node = Node::new(
421 path.clone(),
422 source.clone(),
423 SolData::parse_from(compiler.gcx().sess, s),
424 );
425 (path.clone(), node)
426 });
427 let parsed = parsed.collect::<Vec<_>>();
428
429 Ok(parsed)
430 })
431 }
432
433 fn finalize_imports(
434 &mut self,
435 nodes: &mut Vec<Node<Self::ParsedSource>>,
436 include_paths: &BTreeSet<PathBuf>,
437 ) -> Result<()> {
438 let compiler = &mut self.compiler;
439 compiler.sess_mut().opts.include_paths.extend(include_paths.iter().cloned());
440 compiler.enter_mut(|compiler| {
441 let mut pcx = compiler.parse();
442 pcx.set_resolve_imports(true);
443 pcx.force_resolve_all_imports();
444 });
445
446 if let Some(Err(diag)) = compiler.sess().emitted_errors() {
449 if let Some(idx) = nodes
450 .iter()
451 .position(|node| node.data.parse_result.is_ok())
452 .or_else(|| nodes.first().map(|_| 0))
453 {
454 nodes[idx].data.parse_result = Err(diag.to_string());
455 }
456 }
457
458 for node in nodes.iter() {
459 if let Err(e) = &node.data.parse_result {
460 debug!("failed parsing:\n{e}");
461 }
462 }
463
464 Ok(())
465 }
466}
467
468impl ParsedSource for SolData {
469 type Language = SolcLanguage;
470
471 fn parse(content: &str, file: &std::path::Path) -> Result<Self> {
472 Ok(Self::parse(content, file))
473 }
474
475 fn version_req(&self) -> Option<&semver::VersionReq> {
476 self.version_req.as_ref()
477 }
478
479 fn contract_names(&self) -> &[String] {
480 &self.contract_names
481 }
482
483 fn language(&self) -> Self::Language {
484 if self.is_yul {
485 SolcLanguage::Yul
486 } else {
487 SolcLanguage::Solidity
488 }
489 }
490
491 fn resolve_imports<C>(
492 &self,
493 _paths: &crate::ProjectPathsConfig<C>,
494 _include_paths: &mut BTreeSet<PathBuf>,
495 ) -> Result<Vec<PathBuf>> {
496 Ok(self.imports.iter().map(|i| i.data().path().to_path_buf()).collect())
497 }
498
499 fn compilation_dependencies<'a>(
500 &self,
501 imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
502 ) -> impl Iterator<Item = &'a Path>
503 where
504 Self: 'a,
505 {
506 imported_nodes.filter_map(|(path, node)| (!node.libraries.is_empty()).then_some(path))
507 }
508}
509
510impl CompilationError for Error {
511 fn is_warning(&self) -> bool {
512 self.severity.is_warning()
513 }
514 fn is_error(&self) -> bool {
515 self.severity.is_error()
516 }
517
518 fn source_location(&self) -> Option<SourceLocation> {
519 self.source_location.clone()
520 }
521
522 fn severity(&self) -> Severity {
523 self.severity
524 }
525
526 fn error_code(&self) -> Option<u64> {
527 self.error_code
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use foundry_compilers_artifacts::{CompilerOutput, SolcLanguage};
534 use semver::Version;
535
536 use crate::{
537 buildinfo::RawBuildInfo,
538 compilers::{
539 solc::{SolcCompiler, SolcVersionedInput},
540 CompilerInput,
541 },
542 AggregatedCompilerOutput,
543 };
544
545 #[test]
546 fn can_parse_declaration_error() {
547 let s = r#"{
548 "errors": [
549 {
550 "component": "general",
551 "errorCode": "7576",
552 "formattedMessage": "DeclarationError: Undeclared identifier. Did you mean \"revert\"?\n --> /Users/src/utils/UpgradeProxy.sol:35:17:\n |\n35 | refert(\"Transparent ERC1967 proxies do not have upgradeable implementations\");\n | ^^^^^^\n\n",
553 "message": "Undeclared identifier. Did you mean \"revert\"?",
554 "severity": "error",
555 "sourceLocation": {
556 "end": 1623,
557 "file": "/Users/src/utils/UpgradeProxy.sol",
558 "start": 1617
559 },
560 "type": "DeclarationError"
561 }
562 ],
563 "sources": { }
564}"#;
565
566 let out: CompilerOutput = serde_json::from_str(s).unwrap();
567 assert_eq!(out.errors.len(), 1);
568
569 let out_converted = crate::compilers::CompilerOutput {
570 errors: out.errors,
571 contracts: Default::default(),
572 sources: Default::default(),
573 metadata: Default::default(),
574 };
575
576 let v = Version::new(0, 8, 12);
577 let input = SolcVersionedInput::build(
578 Default::default(),
579 Default::default(),
580 SolcLanguage::Solidity,
581 v.clone(),
582 );
583 let build_info = RawBuildInfo::new(&input, &out_converted, true).unwrap();
584 let mut aggregated = AggregatedCompilerOutput::<SolcCompiler>::default();
585 aggregated.extend(v, build_info, "default", out_converted);
586 assert!(!aggregated.is_unchanged());
587 }
588}