1use super::{
2 restrictions::CompilerSettingsRestrictions, CompilationError, Compiler, CompilerInput,
3 CompilerOutput, CompilerSettings, CompilerVersion, Language, ParsedSource,
4};
5use crate::resolver::parse::SolData;
6pub use foundry_compilers_artifacts::SolcLanguage;
7use foundry_compilers_artifacts::{
8 error::SourceLocation,
9 output_selection::OutputSelection,
10 remappings::Remapping,
11 sources::{Source, Sources},
12 BytecodeHash, Contract, Error, EvmVersion, Settings, Severity, SolcInput,
13};
14use foundry_compilers_core::error::Result;
15use semver::Version;
16use serde::{Deserialize, Serialize};
17use std::{
18 borrow::Cow,
19 collections::{BTreeMap, BTreeSet},
20 ops::{Deref, DerefMut},
21 path::{Path, PathBuf},
22};
23mod compiler;
24pub use compiler::{Solc, SOLC_EXTENSIONS};
25
26#[derive(Clone, Debug)]
27#[cfg_attr(feature = "svm-solc", derive(Default))]
28pub enum SolcCompiler {
29 #[default]
30 #[cfg(feature = "svm-solc")]
31 AutoDetect,
32
33 Specific(Solc),
34}
35
36impl Language for SolcLanguage {
37 const FILE_EXTENSIONS: &'static [&'static str] = SOLC_EXTENSIONS;
38}
39
40impl Compiler for SolcCompiler {
41 type Input = SolcVersionedInput;
42 type CompilationError = Error;
43 type ParsedSource = SolData;
44 type Settings = SolcSettings;
45 type Language = SolcLanguage;
46 type CompilerContract = Contract;
47
48 fn compile(
49 &self,
50 input: &Self::Input,
51 ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>> {
52 let mut solc = match self {
53 Self::Specific(solc) => solc.clone(),
54
55 #[cfg(feature = "svm-solc")]
56 Self::AutoDetect => Solc::find_or_install(&input.version)?,
57 };
58 solc.base_path.clone_from(&input.cli_settings.base_path);
59 solc.allow_paths.clone_from(&input.cli_settings.allow_paths);
60 solc.include_paths.clone_from(&input.cli_settings.include_paths);
61 solc.extra_args.extend_from_slice(&input.cli_settings.extra_args);
62
63 let solc_output = solc.compile(&input.input)?;
64
65 let output = CompilerOutput {
66 errors: solc_output.errors,
67 contracts: solc_output.contracts,
68 sources: solc_output.sources,
69 metadata: BTreeMap::new(),
70 };
71
72 Ok(output)
73 }
74
75 fn available_versions(&self, _language: &Self::Language) -> Vec<CompilerVersion> {
76 match self {
77 Self::Specific(solc) => vec![CompilerVersion::Installed(Version::new(
78 solc.version.major,
79 solc.version.minor,
80 solc.version.patch,
81 ))],
82
83 #[cfg(feature = "svm-solc")]
84 Self::AutoDetect => {
85 let mut all_versions = Solc::installed_versions()
86 .into_iter()
87 .map(CompilerVersion::Installed)
88 .collect::<Vec<_>>();
89 let mut uniques = all_versions
90 .iter()
91 .map(|v| {
92 let v = v.as_ref();
93 (v.major, v.minor, v.patch)
94 })
95 .collect::<std::collections::HashSet<_>>();
96 all_versions.extend(
97 Solc::released_versions()
98 .into_iter()
99 .filter(|v| uniques.insert((v.major, v.minor, v.patch)))
100 .map(CompilerVersion::Remote),
101 );
102 all_versions.sort_unstable();
103 all_versions
104 }
105 }
106 }
107}
108
109#[derive(Clone, Debug, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub struct SolcVersionedInput {
112 pub version: Version,
113 #[serde(flatten)]
114 pub input: SolcInput,
115 #[serde(flatten)]
116 pub cli_settings: CliSettings,
117}
118
119impl CompilerInput for SolcVersionedInput {
120 type Settings = SolcSettings;
121 type Language = SolcLanguage;
122
123 fn build(
128 sources: Sources,
129 settings: Self::Settings,
130 language: Self::Language,
131 version: Version,
132 ) -> Self {
133 let SolcSettings { settings, cli_settings } = settings;
134 let input = SolcInput::new(language, sources, settings).sanitized(&version);
135
136 Self { version, input, cli_settings }
137 }
138
139 fn language(&self) -> Self::Language {
140 self.input.language
141 }
142
143 fn version(&self) -> &Version {
144 &self.version
145 }
146
147 fn sources(&self) -> impl Iterator<Item = (&Path, &Source)> {
148 self.input.sources.iter().map(|(path, source)| (path.as_path(), source))
149 }
150
151 fn compiler_name(&self) -> Cow<'static, str> {
152 "Solc".into()
153 }
154
155 fn strip_prefix(&mut self, base: &Path) {
156 self.input.strip_prefix(base);
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
161#[serde(rename_all = "camelCase")]
162pub struct CliSettings {
163 #[serde(default, skip_serializing_if = "Vec::is_empty")]
164 pub extra_args: Vec<String>,
165 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
166 pub allow_paths: BTreeSet<PathBuf>,
167 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub base_path: Option<PathBuf>,
169 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
170 pub include_paths: BTreeSet<PathBuf>,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
174pub struct SolcSettings {
175 #[serde(flatten)]
177 pub settings: Settings,
178 #[serde(flatten)]
180 pub cli_settings: CliSettings,
181}
182
183impl Deref for SolcSettings {
184 type Target = Settings;
185
186 fn deref(&self) -> &Self::Target {
187 &self.settings
188 }
189}
190
191impl DerefMut for SolcSettings {
192 fn deref_mut(&mut self) -> &mut Self::Target {
193 &mut self.settings
194 }
195}
196
197#[derive(Debug, Clone, Copy, Eq, Default, PartialEq)]
199pub struct Restriction<V> {
200 pub min: Option<V>,
201 pub max: Option<V>,
202}
203
204impl<V: Ord + Copy> Restriction<V> {
205 pub fn satisfies(&self, value: Option<V>) -> bool {
209 self.min.is_none_or(|min| value.is_some_and(|v| v >= min))
210 && self.max.is_none_or(|max| value.is_some_and(|v| v <= max))
211 }
212
213 pub fn merge(self, other: Self) -> Option<Self> {
215 let Self { mut min, mut max } = self;
216 let Self { min: other_min, max: other_max } = other;
217
218 min = min.map_or(other_min, |this_min| {
219 Some(other_min.map_or(this_min, |other_min| this_min.max(other_min)))
220 });
221 max = max.map_or(other_max, |this_max| {
222 Some(other_max.map_or(this_max, |other_max| this_max.min(other_max)))
223 });
224
225 if let (Some(min), Some(max)) = (min, max) {
226 if min > max {
227 return None;
228 }
229 }
230
231 Some(Self { min, max })
232 }
233
234 pub fn apply(&self, value: Option<V>) -> Option<V> {
235 match (value, self.min, self.max) {
236 (None, Some(min), _) => Some(min),
237 (None, None, Some(max)) => Some(max),
238 (Some(cur), Some(min), _) if cur < min => Some(min),
239 (Some(cur), _, Some(max)) if cur > max => Some(max),
240 _ => value,
241 }
242 }
243}
244
245#[derive(Debug, Clone, Copy, Default)]
247pub struct SolcRestrictions {
248 pub evm_version: Restriction<EvmVersion>,
249 pub via_ir: Option<bool>,
250 pub optimizer_runs: Restriction<usize>,
251 pub bytecode_hash: Option<BytecodeHash>,
252}
253
254impl CompilerSettingsRestrictions for SolcRestrictions {
255 fn merge(self, other: Self) -> Option<Self> {
256 if let (Some(via_ir), Some(other_via_ir)) = (self.via_ir, other.via_ir) {
257 if via_ir != other_via_ir {
258 return None;
259 }
260 }
261
262 if let (Some(bytecode_hash), Some(other_bytecode_hash)) =
263 (self.bytecode_hash, other.bytecode_hash)
264 {
265 if bytecode_hash != other_bytecode_hash {
266 return None;
267 }
268 }
269
270 Some(Self {
271 evm_version: self.evm_version.merge(other.evm_version)?,
272 via_ir: self.via_ir.or(other.via_ir),
273 optimizer_runs: self.optimizer_runs.merge(other.optimizer_runs)?,
274 bytecode_hash: self.bytecode_hash.or(other.bytecode_hash),
275 })
276 }
277}
278
279impl CompilerSettings for SolcSettings {
280 type Restrictions = SolcRestrictions;
281
282 fn update_output_selection(&mut self, f: impl FnOnce(&mut OutputSelection) + Copy) {
283 f(&mut self.settings.output_selection)
284 }
285
286 fn can_use_cached(&self, other: &Self) -> bool {
287 let Self {
288 settings:
289 Settings {
290 stop_after,
291 remappings,
292 optimizer,
293 model_checker,
294 metadata,
295 output_selection,
296 evm_version,
297 via_ir,
298 debug,
299 libraries,
300 eof_version,
301 },
302 ..
303 } = self;
304
305 *stop_after == other.settings.stop_after
306 && *remappings == other.settings.remappings
307 && *optimizer == other.settings.optimizer
308 && *model_checker == other.settings.model_checker
309 && *metadata == other.settings.metadata
310 && *evm_version == other.settings.evm_version
311 && *via_ir == other.settings.via_ir
312 && *debug == other.settings.debug
313 && *libraries == other.settings.libraries
314 && *eof_version == other.settings.eof_version
315 && output_selection.is_subset_of(&other.settings.output_selection)
316 }
317
318 fn with_remappings(mut self, remappings: &[Remapping]) -> Self {
319 self.settings.remappings = remappings.to_vec();
320
321 self
322 }
323
324 fn with_allow_paths(mut self, allowed_paths: &BTreeSet<PathBuf>) -> Self {
325 self.cli_settings.allow_paths.clone_from(allowed_paths);
326 self
327 }
328
329 fn with_base_path(mut self, base_path: &Path) -> Self {
330 self.cli_settings.base_path = Some(base_path.to_path_buf());
331 self
332 }
333
334 fn with_include_paths(mut self, include_paths: &BTreeSet<PathBuf>) -> Self {
335 self.cli_settings.include_paths.clone_from(include_paths);
336 self
337 }
338
339 fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool {
340 let mut satisfies = true;
341
342 let SolcRestrictions { evm_version, via_ir, optimizer_runs, bytecode_hash } = restrictions;
343
344 satisfies &= evm_version.satisfies(self.evm_version);
345 satisfies &= via_ir.is_none_or(|via_ir| via_ir == self.via_ir.unwrap_or_default());
346 satisfies &= bytecode_hash.is_none_or(|bytecode_hash| {
347 self.metadata.as_ref().and_then(|m| m.bytecode_hash) == Some(bytecode_hash)
348 });
349 satisfies &= optimizer_runs.satisfies(self.optimizer.runs);
350
351 satisfies &= optimizer_runs
353 .min
354 .is_none_or(|min| min == 0 || self.optimizer.enabled.unwrap_or_default());
355
356 satisfies
357 }
358}
359
360impl ParsedSource for SolData {
361 type Language = SolcLanguage;
362
363 fn parse(content: &str, file: &std::path::Path) -> Result<Self> {
364 Ok(Self::parse(content, file))
365 }
366
367 fn version_req(&self) -> Option<&semver::VersionReq> {
368 self.version_req.as_ref()
369 }
370
371 fn contract_names(&self) -> &[String] {
372 &self.contract_names
373 }
374
375 fn language(&self) -> Self::Language {
376 if self.is_yul {
377 SolcLanguage::Yul
378 } else {
379 SolcLanguage::Solidity
380 }
381 }
382
383 fn resolve_imports<C>(
384 &self,
385 _paths: &crate::ProjectPathsConfig<C>,
386 _include_paths: &mut BTreeSet<PathBuf>,
387 ) -> Result<Vec<PathBuf>> {
388 Ok(self.imports.iter().map(|i| i.data().path().to_path_buf()).collect())
389 }
390
391 fn compilation_dependencies<'a>(
392 &self,
393 imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
394 ) -> impl Iterator<Item = &'a Path>
395 where
396 Self: 'a,
397 {
398 imported_nodes.filter_map(|(path, node)| (!node.libraries.is_empty()).then_some(path))
399 }
400}
401
402impl CompilationError for Error {
403 fn is_warning(&self) -> bool {
404 self.severity.is_warning()
405 }
406 fn is_error(&self) -> bool {
407 self.severity.is_error()
408 }
409
410 fn source_location(&self) -> Option<SourceLocation> {
411 self.source_location.clone()
412 }
413
414 fn severity(&self) -> Severity {
415 self.severity
416 }
417
418 fn error_code(&self) -> Option<u64> {
419 self.error_code
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use foundry_compilers_artifacts::{CompilerOutput, SolcLanguage};
426 use semver::Version;
427
428 use crate::{
429 buildinfo::RawBuildInfo,
430 compilers::{
431 solc::{SolcCompiler, SolcVersionedInput},
432 CompilerInput,
433 },
434 AggregatedCompilerOutput,
435 };
436
437 #[test]
438 fn can_parse_declaration_error() {
439 let s = r#"{
440 "errors": [
441 {
442 "component": "general",
443 "errorCode": "7576",
444 "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",
445 "message": "Undeclared identifier. Did you mean \"revert\"?",
446 "severity": "error",
447 "sourceLocation": {
448 "end": 1623,
449 "file": "/Users/src/utils/UpgradeProxy.sol",
450 "start": 1617
451 },
452 "type": "DeclarationError"
453 }
454 ],
455 "sources": { }
456}"#;
457
458 let out: CompilerOutput = serde_json::from_str(s).unwrap();
459 assert_eq!(out.errors.len(), 1);
460
461 let out_converted = crate::compilers::CompilerOutput {
462 errors: out.errors,
463 contracts: Default::default(),
464 sources: Default::default(),
465 metadata: Default::default(),
466 };
467
468 let v = Version::new(0, 8, 12);
469 let input = SolcVersionedInput::build(
470 Default::default(),
471 Default::default(),
472 SolcLanguage::Solidity,
473 v.clone(),
474 );
475 let build_info = RawBuildInfo::new(&input, &out_converted, true).unwrap();
476 let mut aggregated = AggregatedCompilerOutput::<SolcCompiler>::default();
477 aggregated.extend(v, build_info, "default", out_converted);
478 assert!(!aggregated.is_unchanged());
479 }
480}