Skip to main content

foundry_compilers_artifacts_vyper/
settings.rs

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
36/// Vyper parses `optimize` and `optLevel` through the same optimization level parser.
37pub 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    /// Optimization mode
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub optimize: Option<VyperOptimizationMode>,
92    /// Numeric optimization level
93    #[serde(rename = "optLevel", alias = "opt_level", skip_serializing_if = "Option::is_none")]
94    pub opt_level: Option<VyperOptimizationLevel>,
95    /// Whether or not the bytecode should include Vyper's signature
96    #[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    /// Vyper standard JSON intentionally keeps this key snake_case.
106    #[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    /// Sanitize the output selection.
144    #[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                // During caching we prune output selection for some of the sources, however, Vyper
149                // will reject `[]` as an output selection, so we are adding "abi" as a default
150                // output selection which is cheap to be produced.
151                if selection.is_empty() {
152                    selection.push("abi".to_string())
153                }
154
155                // Unsupported selections.
156                #[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                        // https://github.com/vyperlang/vyper/issues/4389
171                        | "evm.bytecode.linkReferences" | "evm.deployedBytecode.linkReferences"
172                        | "evm.deployedBytecode.immutableReferences"
173                    ) {
174                        return false;
175                    }
176
177                    true
178                });
179            }
180        }
181    }
182
183    /// Sanitize the settings based on the compiler version.
184    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    /// Sanitize the settings based on the compiler version.
194    pub fn sanitized(mut self, version: &Version) -> Self {
195        self.sanitize(version);
196        self
197    }
198
199    /// Adjusts the EVM version based on the compiler version.
200    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}