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#[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 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 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#[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
89fn 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 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}