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 },
301 ..
302 } = self;
303
304 *stop_after == other.settings.stop_after
305 && *remappings == other.settings.remappings
306 && *optimizer == other.settings.optimizer
307 && *model_checker == other.settings.model_checker
308 && *metadata == other.settings.metadata
309 && *evm_version == other.settings.evm_version
310 && *via_ir == other.settings.via_ir
311 && *debug == other.settings.debug
312 && *libraries == other.settings.libraries
313 && output_selection.is_subset_of(&other.settings.output_selection)
314 }
315
316 fn with_remappings(mut self, remappings: &[Remapping]) -> Self {
317 self.settings.remappings = remappings.to_vec();
318
319 self
320 }
321
322 fn with_allow_paths(mut self, allowed_paths: &BTreeSet<PathBuf>) -> Self {
323 self.cli_settings.allow_paths.clone_from(allowed_paths);
324 self
325 }
326
327 fn with_base_path(mut self, base_path: &Path) -> Self {
328 self.cli_settings.base_path = Some(base_path.to_path_buf());
329 self
330 }
331
332 fn with_include_paths(mut self, include_paths: &BTreeSet<PathBuf>) -> Self {
333 self.cli_settings.include_paths.clone_from(include_paths);
334 self
335 }
336
337 fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool {
338 let mut satisfies = true;
339
340 let SolcRestrictions { evm_version, via_ir, optimizer_runs, bytecode_hash } = restrictions;
341
342 satisfies &= evm_version.satisfies(self.evm_version);
343 satisfies &= via_ir.is_none_or(|via_ir| via_ir == self.via_ir.unwrap_or_default());
344 satisfies &= bytecode_hash.is_none_or(|bytecode_hash| {
345 self.metadata.as_ref().and_then(|m| m.bytecode_hash) == Some(bytecode_hash)
346 });
347 satisfies &= optimizer_runs.satisfies(self.optimizer.runs);
348
349 satisfies &= optimizer_runs
351 .min
352 .is_none_or(|min| min == 0 || self.optimizer.enabled.unwrap_or_default());
353
354 satisfies
355 }
356}
357
358impl ParsedSource for SolData {
359 type Language = SolcLanguage;
360
361 fn parse(content: &str, file: &std::path::Path) -> Result<Self> {
362 Ok(Self::parse(content, file))
363 }
364
365 fn version_req(&self) -> Option<&semver::VersionReq> {
366 self.version_req.as_ref()
367 }
368
369 fn contract_names(&self) -> &[String] {
370 &self.contract_names
371 }
372
373 fn language(&self) -> Self::Language {
374 if self.is_yul {
375 SolcLanguage::Yul
376 } else {
377 SolcLanguage::Solidity
378 }
379 }
380
381 fn resolve_imports<C>(
382 &self,
383 _paths: &crate::ProjectPathsConfig<C>,
384 _include_paths: &mut BTreeSet<PathBuf>,
385 ) -> Result<Vec<PathBuf>> {
386 Ok(self.imports.iter().map(|i| i.data().path().to_path_buf()).collect())
387 }
388
389 fn compilation_dependencies<'a>(
390 &self,
391 imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
392 ) -> impl Iterator<Item = &'a Path>
393 where
394 Self: 'a,
395 {
396 imported_nodes.filter_map(|(path, node)| (!node.libraries.is_empty()).then_some(path))
397 }
398}
399
400impl CompilationError for Error {
401 fn is_warning(&self) -> bool {
402 self.severity.is_warning()
403 }
404 fn is_error(&self) -> bool {
405 self.severity.is_error()
406 }
407
408 fn source_location(&self) -> Option<SourceLocation> {
409 self.source_location.clone()
410 }
411
412 fn severity(&self) -> Severity {
413 self.severity
414 }
415
416 fn error_code(&self) -> Option<u64> {
417 self.error_code
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use foundry_compilers_artifacts::{CompilerOutput, SolcLanguage};
424 use semver::Version;
425
426 use crate::{
427 buildinfo::RawBuildInfo,
428 compilers::{
429 solc::{SolcCompiler, SolcVersionedInput},
430 CompilerInput,
431 },
432 AggregatedCompilerOutput,
433 };
434
435 #[test]
436 fn can_parse_declaration_error() {
437 let s = r#"{
438 "errors": [
439 {
440 "component": "general",
441 "errorCode": "7576",
442 "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",
443 "message": "Undeclared identifier. Did you mean \"revert\"?",
444 "severity": "error",
445 "sourceLocation": {
446 "end": 1623,
447 "file": "/Users/src/utils/UpgradeProxy.sol",
448 "start": 1617
449 },
450 "type": "DeclarationError"
451 }
452 ],
453 "sources": { }
454}"#;
455
456 let out: CompilerOutput = serde_json::from_str(s).unwrap();
457 assert_eq!(out.errors.len(), 1);
458
459 let out_converted = crate::compilers::CompilerOutput {
460 errors: out.errors,
461 contracts: Default::default(),
462 sources: Default::default(),
463 metadata: Default::default(),
464 };
465
466 let v = Version::new(0, 8, 12);
467 let input = SolcVersionedInput::build(
468 Default::default(),
469 Default::default(),
470 SolcLanguage::Solidity,
471 v.clone(),
472 );
473 let build_info = RawBuildInfo::new(&input, &out_converted, true).unwrap();
474 let mut aggregated = AggregatedCompilerOutput::<SolcCompiler>::default();
475 aggregated.extend(v, build_info, "default", out_converted);
476 assert!(!aggregated.is_unchanged());
477 }
478}