1use foundry_compilers_artifacts_solc::{
2 EvmVersion, output_selection::OutputSelection, serde_helpers,
3};
4use semver::Version;
5use serde::{Deserialize, Serialize};
6use std::{
7 collections::BTreeSet,
8 path::{Path, PathBuf},
9};
10
11pub const VYPER_SEARCH_PATHS: Version = VYPER_0_4;
12pub const VYPER_BERLIN: Version = Version::new(0, 3, 0);
13pub const VYPER_PARIS: Version = Version::new(0, 3, 7);
14pub const VYPER_SHANGHAI: Version = Version::new(0, 3, 8);
15pub const VYPER_CANCUN: Version = Version::new(0, 3, 8);
16pub const VYPER_PRAGUE: Version = Version::new(0, 4, 3);
17
18const VYPER_0_4: Version = Version::new(0, 4, 0);
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(rename_all = "lowercase")]
22pub enum VyperOptimizationMode {
23 Gas,
24 Codesize,
25 None,
26 #[serde(rename = "O1", alias = "1", alias = "o1")]
27 O1,
28 #[serde(rename = "O2", alias = "2", alias = "o2")]
29 O2,
30 #[serde(rename = "O3", alias = "3", alias = "o3")]
31 O3,
32 #[serde(rename = "Os", alias = "s", alias = "os")]
33 Os,
34}
35
36pub type VyperOptimizationLevel = VyperOptimizationMode;
38
39#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct VyperVenomSettings {
42 #[serde(alias = "disable_inlining", skip_serializing_if = "Option::is_none")]
43 pub disable_inlining: Option<bool>,
44 #[serde(rename = "disableCSE", alias = "disable_cse", skip_serializing_if = "Option::is_none")]
45 pub disable_cse: Option<bool>,
46 #[serde(
47 rename = "disableSCCP",
48 alias = "disable_sccp",
49 skip_serializing_if = "Option::is_none"
50 )]
51 pub disable_sccp: Option<bool>,
52 #[serde(alias = "disable_load_elimination", skip_serializing_if = "Option::is_none")]
53 pub disable_load_elimination: Option<bool>,
54 #[serde(alias = "disable_dead_store_elimination", skip_serializing_if = "Option::is_none")]
55 pub disable_dead_store_elimination: Option<bool>,
56 #[serde(alias = "disable_algebraic_optimization", skip_serializing_if = "Option::is_none")]
57 pub disable_algebraic_optimization: Option<bool>,
58 #[serde(alias = "disable_branch_optimization", skip_serializing_if = "Option::is_none")]
59 pub disable_branch_optimization: Option<bool>,
60 #[serde(alias = "disable_assert_elimination", skip_serializing_if = "Option::is_none")]
61 pub disable_assert_elimination: Option<bool>,
62 #[serde(
63 rename = "disableMem2Var",
64 alias = "disable_mem2var",
65 skip_serializing_if = "Option::is_none"
66 )]
67 pub disable_mem2var: Option<bool>,
68 #[serde(
69 rename = "disableSimplifyCFG",
70 alias = "disable_simplify_cfg",
71 skip_serializing_if = "Option::is_none"
72 )]
73 pub disable_simplify_cfg: Option<bool>,
74 #[serde(alias = "disable_remove_unused_variables", skip_serializing_if = "Option::is_none")]
75 pub disable_remove_unused_variables: Option<bool>,
76 #[serde(alias = "inline_threshold", skip_serializing_if = "Option::is_none")]
77 pub inline_threshold: Option<u64>,
78}
79
80#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
81#[serde(rename_all = "camelCase")]
82pub struct VyperSettings {
83 #[serde(
84 default,
85 with = "serde_helpers::display_from_str_opt",
86 skip_serializing_if = "Option::is_none"
87 )]
88 pub evm_version: Option<EvmVersion>,
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub optimize: Option<VyperOptimizationMode>,
92 #[serde(rename = "optLevel", alias = "opt_level", skip_serializing_if = "Option::is_none")]
94 pub opt_level: Option<VyperOptimizationLevel>,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub bytecode_metadata: Option<bool>,
98 pub output_selection: OutputSelection,
99 #[serde(rename = "search_paths", skip_serializing_if = "Option::is_none")]
100 pub search_paths: Option<BTreeSet<PathBuf>>,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub experimental_codegen: Option<bool>,
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub debug: Option<bool>,
105 #[serde(
107 rename = "enable_decimals",
108 alias = "enableDecimals",
109 skip_serializing_if = "Option::is_none"
110 )]
111 pub enable_decimals: Option<bool>,
112 #[serde(
113 rename = "venomExperimental",
114 alias = "venom_experimental",
115 skip_serializing_if = "Option::is_none"
116 )]
117 pub venom_experimental: Option<bool>,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub venom: Option<VyperVenomSettings>,
120}
121
122impl VyperSettings {
123 pub fn strip_prefix(&mut self, base: &Path) {
124 self.output_selection = OutputSelection(
125 std::mem::take(&mut self.output_selection.0)
126 .into_iter()
127 .map(|(file, selection)| {
128 (
129 Path::new(&file)
130 .strip_prefix(base)
131 .map(|p| p.display().to_string())
132 .unwrap_or(file),
133 selection,
134 )
135 })
136 .collect(),
137 );
138 self.search_paths = self.search_paths.as_ref().map(|paths| {
139 paths.iter().map(|p| p.strip_prefix(base).unwrap_or(p.as_path()).into()).collect()
140 });
141 }
142
143 #[allow(clippy::collapsible_if)]
145 pub fn sanitize_output_selection(&mut self, version: &Version) {
146 for selection in self.output_selection.0.values_mut() {
147 for selection in selection.values_mut() {
148 if selection.is_empty() {
152 selection.push("abi".to_string())
153 }
154
155 #[rustfmt::skip]
157 selection.retain(|selection| {
158 if *version < VYPER_0_4 {
159 if matches!(
160 selection.as_str(),
161 | "evm.bytecode.sourceMap" | "evm.deployedBytecode.sourceMap"
162 ) {
163 return false;
164 }
165 }
166
167 if matches!(
168 selection.as_str(),
169 | "evm.bytecode.sourceMap" | "evm.deployedBytecode.sourceMap"
170 | "evm.bytecode.linkReferences" | "evm.deployedBytecode.linkReferences"
172 | "evm.deployedBytecode.immutableReferences"
173 ) {
174 return false;
175 }
176
177 true
178 });
179 }
180 }
181 }
182
183 pub fn sanitize(&mut self, version: &Version) {
185 if version < &VYPER_SEARCH_PATHS {
186 self.search_paths = None;
187 }
188
189 self.sanitize_output_selection(version);
190 self.normalize_evm_version(version);
191 }
192
193 pub fn sanitized(mut self, version: &Version) -> Self {
195 self.sanitize(version);
196 self
197 }
198
199 pub fn normalize_evm_version(&mut self, version: &Version) {
201 if let Some(evm_version) = &mut self.evm_version {
202 *evm_version = if *evm_version >= EvmVersion::Prague && *version >= VYPER_PRAGUE {
203 EvmVersion::Prague
204 } else if *evm_version >= EvmVersion::Cancun && *version >= VYPER_CANCUN {
205 EvmVersion::Cancun
206 } else if *evm_version >= EvmVersion::Shanghai && *version >= VYPER_SHANGHAI {
207 EvmVersion::Shanghai
208 } else if *evm_version >= EvmVersion::Paris && *version >= VYPER_PARIS {
209 EvmVersion::Paris
210 } else if *evm_version >= EvmVersion::Berlin && *version >= VYPER_BERLIN {
211 EvmVersion::Berlin
212 } else {
213 *evm_version
214 };
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use serde_json::json;
223
224 #[test]
225 fn serializes_extended_vyper_settings() {
226 let settings = VyperSettings {
227 opt_level: Some(VyperOptimizationLevel::Os),
228 debug: Some(true),
229 enable_decimals: Some(true),
230 venom_experimental: Some(true),
231 venom: Some(VyperVenomSettings {
232 disable_inlining: Some(true),
233 disable_cse: Some(true),
234 disable_sccp: Some(false),
235 disable_load_elimination: Some(true),
236 disable_dead_store_elimination: Some(false),
237 disable_algebraic_optimization: Some(true),
238 disable_branch_optimization: Some(false),
239 disable_assert_elimination: Some(true),
240 disable_mem2var: Some(false),
241 disable_simplify_cfg: Some(true),
242 disable_remove_unused_variables: Some(false),
243 inline_threshold: Some(15),
244 }),
245 ..Default::default()
246 };
247
248 let value = serde_json::to_value(settings).unwrap();
249
250 assert_eq!(value["optLevel"], json!("Os"));
251 assert_eq!(value["debug"], json!(true));
252 assert_eq!(value["enable_decimals"], json!(true));
253 assert_eq!(value["venomExperimental"], json!(true));
254 assert_eq!(value["venom"]["disableInlining"], json!(true));
255 assert_eq!(value["venom"]["disableCSE"], json!(true));
256 assert_eq!(value["venom"]["disableSCCP"], json!(false));
257 assert_eq!(value["venom"]["disableMem2Var"], json!(false));
258 assert_eq!(value["venom"]["disableSimplifyCFG"], json!(true));
259 assert_eq!(value["venom"]["disableAssertElimination"], json!(true));
260 assert_eq!(value["venom"]["inlineThreshold"], json!(15));
261 }
262
263 #[test]
264 fn deserializes_optimization_level_aliases() {
265 assert_eq!(
266 serde_json::from_value::<VyperOptimizationMode>(json!("1")).unwrap(),
267 VyperOptimizationMode::O1
268 );
269 assert_eq!(
270 serde_json::from_value::<VyperOptimizationMode>(json!("O2")).unwrap(),
271 VyperOptimizationMode::O2
272 );
273 assert_eq!(
274 serde_json::from_value::<VyperOptimizationMode>(json!("o3")).unwrap(),
275 VyperOptimizationMode::O3
276 );
277 assert_eq!(serde_json::to_value(VyperOptimizationMode::O1).unwrap(), json!("O1"));
278 assert_eq!(
279 serde_json::from_value::<VyperOptimizationLevel>(json!("s")).unwrap(),
280 VyperOptimizationLevel::Os
281 );
282 assert_eq!(serde_json::to_value(VyperOptimizationLevel::O3).unwrap(), json!("O3"));
283 }
284
285 #[test]
286 fn deserializes_snake_case_config_aliases() {
287 let value = json!({
288 "outputSelection": {},
289 "opt_level": "3",
290 "enableDecimals": true,
291 "venom_experimental": true,
292 "venom": {
293 "disable_cse": true,
294 "disable_sccp": false,
295 "disable_mem2var": true,
296 "disable_simplify_cfg": false,
297 "inline_threshold": 15
298 }
299 });
300
301 let settings = serde_json::from_value::<VyperSettings>(value).unwrap();
302
303 assert_eq!(settings.opt_level, Some(VyperOptimizationLevel::O3));
304 assert_eq!(settings.enable_decimals, Some(true));
305 assert_eq!(settings.venom_experimental, Some(true));
306 let venom = settings.venom.unwrap();
307 assert_eq!(venom.disable_cse, Some(true));
308 assert_eq!(venom.disable_sccp, Some(false));
309 assert_eq!(venom.disable_mem2var, Some(true));
310 assert_eq!(venom.disable_simplify_cfg, Some(false));
311 assert_eq!(venom.inline_threshold, Some(15));
312 }
313}