foundry_config/
compilation.rs

1use crate::{filter::GlobMatcher, serde_helpers};
2use foundry_compilers::{
3    RestrictionsWithVersion,
4    artifacts::{BytecodeHash, EvmVersion},
5    multi::{MultiCompilerRestrictions, MultiCompilerSettings},
6    settings::VyperRestrictions,
7    solc::{Restriction, SolcRestrictions},
8};
9use semver::VersionReq;
10use serde::{Deserialize, Deserializer, Serialize};
11
12/// Keeps possible overrides for default settings which users may configure to construct additional
13/// settings profile.
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
15pub struct SettingsOverrides {
16    pub name: String,
17    pub via_ir: Option<bool>,
18    #[serde(default, with = "serde_helpers::display_from_str_opt")]
19    pub evm_version: Option<EvmVersion>,
20    pub optimizer: Option<bool>,
21    pub optimizer_runs: Option<usize>,
22    pub bytecode_hash: Option<BytecodeHash>,
23}
24
25impl SettingsOverrides {
26    /// Applies the overrides to the given settings.
27    pub fn apply(&self, settings: &mut MultiCompilerSettings) {
28        if let Some(via_ir) = self.via_ir {
29            settings.solc.via_ir = Some(via_ir);
30        }
31
32        if let Some(evm_version) = self.evm_version {
33            settings.solc.evm_version = Some(evm_version);
34            settings.vyper.evm_version = Some(evm_version);
35        }
36
37        if let Some(enabled) = self.optimizer {
38            settings.solc.optimizer.enabled = Some(enabled);
39        }
40
41        if let Some(optimizer_runs) = self.optimizer_runs {
42            settings.solc.optimizer.runs = Some(optimizer_runs);
43            // Enable optimizer in optimizer runs set to a higher value than 0.
44            if optimizer_runs > 0 && self.optimizer.is_none() {
45                settings.solc.optimizer.enabled = Some(true);
46            }
47        }
48
49        if let Some(bytecode_hash) = self.bytecode_hash {
50            if let Some(metadata) = settings.solc.metadata.as_mut() {
51                metadata.bytecode_hash = Some(bytecode_hash);
52            } else {
53                settings.solc.metadata = Some(bytecode_hash.into());
54            }
55        }
56    }
57}
58
59#[derive(Debug, thiserror::Error)]
60pub enum RestrictionsError {
61    #[error("specified both exact and relative restrictions for {0}")]
62    BothExactAndRelative(&'static str),
63}
64
65/// Restrictions for compilation of given paths.
66///
67/// Only purpose of this type is to accept user input to later construct
68/// `RestrictionsWithVersion<MultiCompilerRestrictions>`.
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70pub struct CompilationRestrictions {
71    pub paths: GlobMatcher,
72    #[serde(default, deserialize_with = "deserialize_version_req")]
73    pub version: Option<VersionReq>,
74    pub via_ir: Option<bool>,
75    pub bytecode_hash: Option<BytecodeHash>,
76
77    pub min_optimizer_runs: Option<usize>,
78    pub optimizer_runs: Option<usize>,
79    pub max_optimizer_runs: Option<usize>,
80
81    #[serde(default, with = "serde_helpers::display_from_str_opt")]
82    pub min_evm_version: Option<EvmVersion>,
83    #[serde(default, with = "serde_helpers::display_from_str_opt")]
84    pub evm_version: Option<EvmVersion>,
85    #[serde(default, with = "serde_helpers::display_from_str_opt")]
86    pub max_evm_version: Option<EvmVersion>,
87}
88
89/// Custom deserializer for version field that rejects ambiguous bare version numbers.
90fn deserialize_version_req<'de, D>(deserializer: D) -> Result<Option<VersionReq>, D::Error>
91where
92    D: Deserializer<'de>,
93{
94    let opt_string: Option<String> = Option::deserialize(deserializer)?;
95    let Some(opt_string) = opt_string else {
96        return Ok(None);
97    };
98
99    let version = opt_string.trim();
100    // Reject bare versions like "0.8.11" that lack an operator prefix
101    if version.chars().next().is_some_and(|c| c.is_ascii_digit()) {
102        return Err(serde::de::Error::custom(format!(
103            "Invalid version format '{opt_string}' in compilation_restrictions. \
104             Bare version numbers are ambiguous and default to caret requirements (e.g. '^{version}'). \
105             Use an explicit constraint such as '={version}' for an exact version or '>={version}' for a minimum version."
106        )));
107    }
108
109    let req = VersionReq::parse(&opt_string).map_err(|e| {
110        serde::de::Error::custom(format!(
111            "Invalid version requirement '{opt_string}': {e}. \
112             Examples: '=0.8.11' (exact), '>=0.8.11' (minimum), '>=0.8.11 <0.9.0' (range)."
113        ))
114    })?;
115
116    Ok(Some(req))
117}
118
119impl TryFrom<CompilationRestrictions> for RestrictionsWithVersion<MultiCompilerRestrictions> {
120    type Error = RestrictionsError;
121
122    fn try_from(value: CompilationRestrictions) -> Result<Self, Self::Error> {
123        let (min_evm, max_evm) =
124            match (value.min_evm_version, value.max_evm_version, value.evm_version) {
125                (None, None, Some(exact)) => (Some(exact), Some(exact)),
126                (min, max, None) => (min, max),
127                _ => return Err(RestrictionsError::BothExactAndRelative("evm_version")),
128            };
129        let (min_opt, max_opt) =
130            match (value.min_optimizer_runs, value.max_optimizer_runs, value.optimizer_runs) {
131                (None, None, Some(exact)) => (Some(exact), Some(exact)),
132                (min, max, None) => (min, max),
133                _ => return Err(RestrictionsError::BothExactAndRelative("optimizer_runs")),
134            };
135        Ok(Self {
136            restrictions: MultiCompilerRestrictions {
137                solc: SolcRestrictions {
138                    evm_version: Restriction { min: min_evm, max: max_evm },
139                    via_ir: value.via_ir,
140                    optimizer_runs: Restriction { min: min_opt, max: max_opt },
141                    bytecode_hash: value.bytecode_hash,
142                },
143                vyper: VyperRestrictions {
144                    evm_version: Restriction { min: min_evm, max: max_evm },
145                },
146            },
147            version: value.version,
148        })
149    }
150}