foundry_config/providers/
warnings.rs1use crate::{Config, DEPRECATIONS, Warning};
2use figment::{
3 Error, Figment, Metadata, Profile, Provider,
4 value::{Dict, Map, Value},
5};
6use heck::ToSnakeCase;
7use std::collections::{BTreeMap, BTreeSet};
8
9const COMPILATION_RESTRICTIONS_KEYS: &[&str] = &[
11 "paths",
12 "version",
13 "via_ir",
14 "bytecode_hash",
15 "min_optimizer_runs",
16 "optimizer_runs",
17 "max_optimizer_runs",
18 "min_evm_version",
19 "evm_version",
20 "max_evm_version",
21];
22
23const SETTINGS_OVERRIDES_KEYS: &[&str] =
25 &["name", "via_ir", "evm_version", "optimizer", "optimizer_runs", "bytecode_hash"];
26
27const VYPER_KEYS: &[&str] = &[
31 "optimize",
32 "opt_level",
33 "optLevel",
34 "path",
35 "experimental_codegen",
36 "venom_experimental",
37 "debug",
38 "enable_decimals",
39 "venom",
40];
41
42const DOC_KEYS: &[&str] = &["out", "title", "book", "homepage", "repository", "path", "ignore"];
46
47const SYMBOLIC_KEYS: &[&str] = &[
51 "enabled",
52 "solver",
53 "solver_command",
54 "solver_portfolio",
55 "timeout",
56 "loop",
57 "depth",
58 "width",
59 "max_depth",
60 "max_paths",
61 "invariant_depth",
62 "exploration_order",
63 "max_solver_queries",
64 "default_dynamic_length",
65 "max_dynamic_length",
66 "array_lengths",
67 "dynamic_lengths",
68 "default_array_lengths",
69 "default_bytes_lengths",
70 "max_calldata_bytes",
71 "symbolic_call_targets",
72 "dump_smt",
73 "storage_layout",
74];
75
76const RESERVED_KEYS: &[&str] = &["extends"];
78
79const BACKWARD_COMPATIBLE_KEYS: &[&str] = &["solc_version", "tempo", "optimism"];
84
85pub struct WarningsProvider<P> {
87 provider: P,
88 profile: Profile,
89 old_warnings: Result<Vec<Warning>, Error>,
90}
91
92impl<P: Provider> WarningsProvider<P> {
93 const WARNINGS_KEY: &'static str = "__warnings";
94
95 pub fn new(
97 provider: P,
98 profile: impl Into<Profile>,
99 old_warnings: Result<Vec<Warning>, Error>,
100 ) -> Self {
101 Self { provider, profile: profile.into(), old_warnings }
102 }
103
104 pub fn for_figment(provider: P, figment: &Figment) -> Self {
106 let old_warnings = {
107 let warnings_res = figment.extract_inner(Self::WARNINGS_KEY);
108 if warnings_res.as_ref().err().map(|err| err.missing()).unwrap_or(false) {
109 Ok(vec![])
110 } else {
111 warnings_res
112 }
113 };
114 Self::new(provider, figment.profile().clone(), old_warnings)
115 }
116
117 pub fn collect_warnings(&self) -> Result<Vec<Warning>, Error> {
119 let data = self.provider.data().unwrap_or_default();
120
121 let mut out = self.old_warnings.clone()?;
122
123 out.extend(data.keys().filter(|k| !Config::is_standalone_section(k.as_str())).map(
125 |unknown_section| {
126 let source = self.provider.metadata().source.map(|s| s.to_string());
127 Warning::UnknownSection { unknown_section: unknown_section.clone(), source }
128 },
129 ));
130
131 let deprecated_key_warning = |key| {
133 DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| {
134 (key == *deprecated_key).then(|| Warning::DeprecatedKey {
135 old: deprecated_key.to_string(),
136 new: new_value.to_string(),
137 })
138 })
139 };
140 let profiles = data
141 .iter()
142 .filter(|(profile, _)| **profile == Config::PROFILE_SECTION)
143 .map(|(_, dict)| dict);
144
145 out.extend(profiles.clone().flat_map(BTreeMap::keys).filter_map(deprecated_key_warning));
146 out.extend(
147 profiles
148 .clone()
149 .filter_map(|dict| dict.get(self.profile.as_str().as_str()))
150 .filter_map(Value::as_dict)
151 .flat_map(BTreeMap::keys)
152 .filter_map(deprecated_key_warning),
153 );
154
155 if let Ok(default_map) = figment::providers::Serialized::defaults(&Config::default()).data()
157 && let Some(default_dict) = default_map.get(&Config::DEFAULT_PROFILE)
158 {
159 let allowed_keys: BTreeSet<String> = default_dict.keys().cloned().collect();
160 for profile_map in profiles.clone() {
161 for (profile, value) in profile_map {
162 let Some(profile_dict) = value.as_dict() else {
163 continue;
164 };
165
166 let source = self
167 .provider
168 .metadata()
169 .source
170 .map(|s| s.to_string())
171 .unwrap_or(Config::FILE_NAME.to_string());
172 for key in profile_dict.keys() {
173 let is_not_deprecated =
174 !DEPRECATIONS.iter().any(|(deprecated_key, _)| *deprecated_key == key);
175 let is_not_allowed = !allowed_keys.contains(key)
176 && !allowed_keys.contains(&key.to_snake_case());
177 let is_not_reserved =
178 !RESERVED_KEYS.contains(&key.as_str()) && key != Self::WARNINGS_KEY;
179 let is_not_backward_compatible =
180 !BACKWARD_COMPATIBLE_KEYS.contains(&key.as_str());
181
182 if is_not_deprecated
183 && is_not_allowed
184 && is_not_reserved
185 && is_not_backward_compatible
186 {
187 out.push(Warning::UnknownKey {
188 key: key.clone(),
189 profile: profile.clone(),
190 source: source.clone(),
191 });
192 }
193 }
194
195 self.collect_nested_section_warnings(
197 profile_dict,
198 default_dict,
199 &source,
200 &mut out,
201 );
202 }
203 }
204
205 self.collect_standalone_section_warnings(&data, default_dict, &mut out);
207 }
208
209 Ok(out)
210 }
211
212 fn collect_standalone_section_warnings(
214 &self,
215 data: &Map<Profile, Dict>,
216 default_dict: &Dict,
217 out: &mut Vec<Warning>,
218 ) {
219 let source = self
220 .provider
221 .metadata()
222 .source
223 .map(|s| s.to_string())
224 .unwrap_or(Config::FILE_NAME.to_string());
225
226 for section_name in Config::STANDALONE_SECTIONS {
227 let section_profile = Profile::new(section_name);
229 let Some(section_dict) = data.get(§ion_profile) else {
230 continue;
231 };
232
233 let allowed_keys: BTreeSet<String> = if *section_name == "vyper" {
237 VYPER_KEYS.iter().map(|s| s.to_string()).collect()
238 } else if *section_name == "doc" {
239 DOC_KEYS.iter().map(|s| s.to_string()).collect()
240 } else {
241 let Some(default_section_value) = default_dict.get(*section_name) else {
242 continue;
243 };
244 let Some(default_section_dict) = default_section_value.as_dict() else {
245 continue;
246 };
247 default_section_dict.keys().cloned().collect()
248 };
249
250 for key in section_dict.keys() {
251 let is_not_allowed =
252 !allowed_keys.contains(key) && !allowed_keys.contains(&key.to_snake_case());
253 if is_not_allowed {
254 out.push(Warning::UnknownSectionKey {
255 key: key.clone(),
256 section: section_name.to_string(),
257 source: source.clone(),
258 });
259 }
260 }
261 }
262 }
263
264 fn collect_nested_section_warnings(
267 &self,
268 profile_dict: &Dict,
269 default_dict: &Dict,
270 source: &str,
271 out: &mut Vec<Warning>,
272 ) {
273 for (key, value) in profile_dict {
275 let Some(nested_dict) = value.as_dict() else {
276 if let Some(arr) = value.as_array() {
278 let allowed_keys = Self::get_array_item_allowed_keys(key);
280
281 if allowed_keys.is_empty() {
282 continue;
283 }
284
285 for item in arr {
286 let Some(item_dict) = item.as_dict() else {
287 continue;
288 };
289 for item_key in item_dict.keys() {
290 let is_not_allowed = !allowed_keys.contains(item_key)
291 && !allowed_keys.contains(&item_key.to_snake_case());
292 if is_not_allowed {
293 out.push(Warning::UnknownSectionKey {
294 key: item_key.clone(),
295 section: key.clone(),
296 source: source.to_string(),
297 });
298 }
299 }
300 }
301 }
302 continue;
303 };
304
305 let allowed_keys: BTreeSet<String> = if key == "vyper" {
309 VYPER_KEYS.iter().map(|s| s.to_string()).collect()
310 } else if key == "doc" {
311 DOC_KEYS.iter().map(|s| s.to_string()).collect()
312 } else if key == "symbolic" {
313 SYMBOLIC_KEYS.iter().map(|s| s.to_string()).collect()
314 } else {
315 let Some(default_value) = default_dict.get(key) else {
316 continue;
317 };
318 let Some(default_nested_dict) = default_value.as_dict() else {
319 continue;
320 };
321 default_nested_dict.keys().cloned().collect()
322 };
323
324 for nested_key in nested_dict.keys() {
325 let is_not_allowed = !allowed_keys.contains(nested_key)
326 && !allowed_keys.contains(&nested_key.to_snake_case());
327 if is_not_allowed {
328 out.push(Warning::UnknownSectionKey {
329 key: nested_key.clone(),
330 section: key.clone(),
331 source: source.to_string(),
332 });
333 }
334 }
335 }
336 }
337
338 fn get_array_item_allowed_keys(section_name: &str) -> BTreeSet<String> {
340 match section_name {
341 "compilation_restrictions" => {
342 COMPILATION_RESTRICTIONS_KEYS.iter().map(|s| s.to_string()).collect()
343 }
344 "additional_compiler_profiles" => {
345 SETTINGS_OVERRIDES_KEYS.iter().map(|s| s.to_string()).collect()
346 }
347 _ => BTreeSet::new(),
348 }
349 }
350}
351
352impl<P: Provider> Provider for WarningsProvider<P> {
353 fn metadata(&self) -> Metadata {
354 if let Some(source) = self.provider.metadata().source {
355 Metadata::from("Warnings", source)
356 } else {
357 Metadata::named("Warnings")
358 }
359 }
360
361 fn data(&self) -> Result<Map<Profile, Dict>, Error> {
362 let warnings = self.collect_warnings()?;
363 Ok(Map::from([(
364 self.profile.clone(),
365 Dict::from([(Self::WARNINGS_KEY.to_string(), Value::serialize(warnings)?)]),
366 )]))
367 }
368
369 fn profile(&self) -> Option<Profile> {
370 Some(self.profile.clone())
371 }
372}